@maccesar/titools 4.3.0 → 4.4.2
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 +15 -12
- package/package.json +1 -1
- package/skills/purgetss/SKILL.md +4 -9
- package/skills/ti-api/references/api-core.md +24 -69
- package/skills/ti-api/references/api-data-network.md +18 -63
- package/skills/ti-api/references/api-media.md +15 -49
- package/skills/ti-api/references/api-modules-coremotion-urlsession.md +6 -20
- package/skills/ti-api/references/api-modules-map.md +17 -40
- package/skills/ti-api/references/api-modules-nfc.md +23 -56
- package/skills/ti-api/references/api-ui-android.md +16 -37
- package/skills/ti-api/references/api-ui-extras.md +28 -72
- package/skills/ti-api/references/api-ui-lists.md +14 -35
- package/skills/ti-api/references/api-ui-text-input.md +13 -39
- package/skills/ti-api/references/api-ui-windows-navigation.md +17 -43
- package/skills/ti-api/references/api-xml-global.md +25 -60
- package/skills/ti-expert/SKILL.md +35 -5
- package/skills/ti-expert/references/adaptive-layouts.md +15 -43
- package/skills/ti-expert/references/alloy-builtins.md +8 -22
- package/skills/ti-expert/references/alloy-structure.md +34 -18
- package/skills/ti-expert/references/anti-patterns.md +18 -39
- package/skills/ti-expert/references/code-conventions.md +5 -3
- package/skills/ti-expert/references/controller-patterns.md +31 -26
- package/skills/ti-expert/references/examples.md +41 -21
- package/skills/ti-expert/references/feedback-migration.md +143 -0
- package/skills/ti-expert/references/feedback-surfaces.md +111 -0
- package/skills/ti-expert/references/feedback-widget-contracts.md +175 -0
- package/skills/ti-expert/references/file-type-association.md +265 -0
- package/skills/ti-expert/references/migration-patterns.md +37 -25
- package/skills/ti-expert/references/security-device.md +58 -56
- package/skills/ti-expert/references/sharing.md +227 -0
- package/skills/ti-expert/references/state-management.md +5 -5
- package/skills/ti-expert/references/theming.md +109 -392
- package/skills/ti-guides/references/coding-best-practices.md +1 -2
- package/skills/ti-guides/references/style-and-conventions.md +1 -2
- package/skills/ti-howtos/references/debugging-profiling.md +1 -2
- package/skills/ti-howtos/references/location-and-maps.md +1 -2
- package/skills/ti-howtos/references/using-modules.md +1 -2
- package/skills/ti-howtos/references/web-content-integration.md +1 -2
- package/skills/ti-howtos/references/webpack-build-pipeline.md +1 -2
- package/skills/ti-ui/references/listviews-and-performance.md +5 -10
- package/skills/ti-ui/references/orientation.md +1 -2
|
@@ -1,46 +1,35 @@
|
|
|
1
1
|
# Titanium Alloy anti-patterns
|
|
2
2
|
|
|
3
3
|
## 1. Inline styling instead of TSS
|
|
4
|
-
**Symptom:** Using inline attributes like `backgroundColor="#fff"` directly in XML views.
|
|
5
|
-
**Problem:** Inline attributes make theme changes impossible and scatter visual definitions across views.
|
|
6
|
-
**Solution:** Define styles in TSS files using IDs or class selectors.
|
|
4
|
+
**Symptom:** Using inline attributes like `backgroundColor="#fff"` directly in XML views. **Problem:** Inline attributes make theme changes impossible and scatter visual definitions across views. **Solution:** Define styles in TSS files using IDs or class selectors.
|
|
7
5
|
|
|
8
6
|
## 2. Fat controllers
|
|
9
|
-
**Symptom:** Controllers with 100+ lines handling logic, API, and UI.
|
|
10
|
-
**Solution:** Delegate business logic to `lib/services/` and API calls to `lib/api/`.
|
|
7
|
+
**Symptom:** Controllers with 100+ lines handling logic, API, and UI. **Solution:** Delegate business logic to `lib/services/` and API calls to `lib/api/`.
|
|
11
8
|
|
|
12
9
|
## 3. Memory leaks (Missing Cleanup)
|
|
13
|
-
**Symptom:** Adding `Ti.App` or `Alloy.Collections` listeners without a `cleanup()` function.
|
|
14
|
-
**Solution:** Always implement `$.cleanup = cleanup` and remove listeners there.
|
|
10
|
+
**Symptom:** Adding `Ti.App` or `Alloy.Collections` listeners without a `cleanup()` function. **Solution:** Always implement `$.cleanup = cleanup` and remove listeners there.
|
|
15
11
|
|
|
16
12
|
## 4. Direct native module calls
|
|
17
|
-
**Symptom:** Calling `require('ti.module')` directly in a controller.
|
|
18
|
-
**Solution:** Wrap it in a service in `lib/services/` (e.g., `audioService.js`).
|
|
13
|
+
**Symptom:** Calling `require('ti.module')` directly in a controller. **Solution:** Wrap it in a service in `lib/services/` (e.g., `audioService.js`).
|
|
19
14
|
|
|
20
15
|
## 5. Direct controller navigation
|
|
21
|
-
**Symptom:** `Alloy.createController('name').getView().open()`.
|
|
22
|
-
**Solution:** Use a Navigation Service to centralize `open/close` and trigger the `cleanup()` function automatically.
|
|
16
|
+
**Symptom:** `Alloy.createController('name').getView().open()`. **Solution:** Use a Navigation Service to centralize `open/close` and trigger the `cleanup()` function automatically.
|
|
23
17
|
|
|
24
18
|
## 6. Complex matrix animations
|
|
25
|
-
**Symptom:** Manual use of `Ti.UI.create2DMatrix()` for simple animations.
|
|
26
|
-
**Solution:** Use `Ti.UI.createAnimation()` for opacity, transform, and duration-based animations. Reserve 2D Matrix for complex multi-property transforms.
|
|
19
|
+
**Symptom:** Manual use of `Ti.UI.create2DMatrix()` for simple animations. **Solution:** Use `Ti.UI.createAnimation()` for opacity, transform, and duration-based animations. Reserve 2D Matrix for complex multi-property transforms.
|
|
27
20
|
|
|
28
21
|
## 7. Hardcoded strings & missing a11y
|
|
29
|
-
**Symptom:** `text="Login"` instead of `text="L('login')"`, or missing `accessibilityLabel`.
|
|
30
|
-
**Solution:** Always use i18n and accessibility properties.
|
|
22
|
+
**Symptom:** `text="Login"` instead of `text="L('login')"`, or missing `accessibilityLabel`. **Solution:** Always use i18n and accessibility properties.
|
|
31
23
|
|
|
32
24
|
## 8. Logic in TSS
|
|
33
|
-
**Symptom:** Using conditionals or calculations inside TSS.
|
|
34
|
-
**Solution:** Keep styling declarative in TSS files.
|
|
25
|
+
**Symptom:** Using conditionals or calculations inside TSS. **Solution:** Keep styling declarative in TSS files.
|
|
35
26
|
|
|
36
27
|
---
|
|
37
28
|
|
|
38
29
|
## Titanium layout anti-patterns
|
|
39
30
|
|
|
40
31
|
## 9. Padding on container views
|
|
41
|
-
**Symptom:** Setting `padding` on View, Window, ScrollView, or TableView.
|
|
42
|
-
**Problem:** Base container elements don't support padding in Titanium.
|
|
43
|
-
**Solution:** Use margins on children instead:
|
|
32
|
+
**Symptom:** Setting `padding` on View, Window, ScrollView, or TableView. **Problem:** Base container elements don't support padding in Titanium. **Solution:** Use margins on children instead:
|
|
44
33
|
```tss
|
|
45
34
|
/* WRONG */
|
|
46
35
|
"#container": { padding: 16 }
|
|
@@ -50,31 +39,21 @@
|
|
|
50
39
|
```
|
|
51
40
|
|
|
52
41
|
## 10. Redundant composite layout
|
|
53
|
-
**Symptom:** Adding `layout: 'composite'` to Views.
|
|
54
|
-
**Problem:** Composite (absolute positioning) is the DEFAULT layout. Setting it is redundant.
|
|
55
|
-
**Solution:** Omit `layout` for composite, only specify `layout: 'horizontal'` or `layout: 'vertical'` when needed.
|
|
42
|
+
**Symptom:** Adding `layout: 'composite'` to Views. **Problem:** Composite (absolute positioning) is the DEFAULT layout. Setting it is redundant. **Solution:** Omit `layout` for composite, only specify `layout: 'horizontal'` or `layout: 'vertical'` when needed.
|
|
56
43
|
|
|
57
44
|
## 11. Using `lib/` prefix in require statements
|
|
58
|
-
**Symptom:** `const service = require('lib/services/picsum')`
|
|
59
|
-
**Problem:** Alloy flattens the `lib/` folder during build. Files in `app/lib/services/` become `Resources/iphone/services/`.
|
|
60
|
-
**Solution:** Omit the `lib/` prefix: `const service = require('services/picsum')`
|
|
45
|
+
**Symptom:** `const service = require('lib/services/picsum')` **Problem:** Alloy flattens the `lib/` folder during build. Files in `app/lib/services/` become `Resources/iphone/services/`. **Solution:** Omit the `lib/` prefix: `const service = require('services/picsum')`
|
|
61
46
|
|
|
62
47
|
## 12. Wrong window ID in controller
|
|
63
|
-
**Symptom:** Using `$.index.open()` when the Window has `id="mainWindow"`.
|
|
64
|
-
**Problem:** Alloy generates `$` references from XML IDs. If Window is `id="mainWindow"`, `$.index` doesn't exist.
|
|
65
|
-
**Solution:** Match the ID: `$.mainWindow.open()`
|
|
48
|
+
**Symptom:** Using `$.index.open()` when the Window has `id="mainWindow"`. **Problem:** Alloy generates `$` references from XML IDs. If Window is `id="mainWindow"`, `$.index` doesn't exist. **Solution:** Match the ID: `$.mainWindow.open()`
|
|
66
49
|
|
|
67
50
|
## 13. Using `Ti.UI.createNotification`
|
|
68
|
-
**Symptom:** `Ti.UI.createNotification({ message: 'Hi' }).show()`
|
|
69
|
-
**Problem:** This API doesn't exist in Titanium. Causes "invalid method" error.
|
|
70
|
-
**Solution:** Use `Ti.UI.createAlertDialog` for simple messages, or create custom toast views.
|
|
51
|
+
**Symptom:** `Ti.UI.createNotification({ message: 'Hi' }).show()` **Problem:** This API doesn't exist in Titanium. Causes "invalid method" error. **Solution:** Classify the feedback before choosing UI. Use the app's Snackbar Widget for transient foreground feedback, inline/Banner state for persistent conditions, and an app-owned Dialog only for blocking or critical information. Use the supported platform notification APIs only for actual OS notifications. See [Feedback Surfaces](feedback-surfaces.md).
|
|
71
52
|
|
|
72
53
|
## 14. Using nonexistent iOS share APIs
|
|
73
|
-
**Symptom:** `Ti.UI.iOS.createActivityPopover` or `alloy/social` with wrong methods.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- iOS: Use `Ti.UI.iOS.createDocumentViewer` for files, or simple `Ti.UI.createOptionDialog` + `Ti.UI.Clipboard` for links
|
|
77
|
-
- Android: Use `Ti.Android.createIntent` with ACTION_SEND
|
|
54
|
+
**Symptom:** `Ti.UI.iOS.createActivityPopover` or `alloy/social` with wrong methods. **Problem:** These APIs either don't exist or have changed. Causes runtime errors. **Solution:**
|
|
55
|
+
- Keep sharing system-owned. Use the currently supported Titanium share/document API or native module for the target platform and verify its current contract with the `ti-api` skill.
|
|
56
|
+
- Do not imitate a share sheet with an app-owned `OptionDialog`. A Bottom Sheet may choose an app action such as an export format, but the OS share surface owns the final destination.
|
|
78
57
|
|
|
79
58
|
## Community-Discovered Patterns
|
|
80
59
|
|
|
@@ -117,8 +96,8 @@ This applies to Windows inside both standalone NavigationWindow and TabGroup (wh
|
|
|
117
96
|
| ----------------------- | ------------------------ | --------------------- |
|
|
118
97
|
| `padding` on View | No padding on containers | Margins on children |
|
|
119
98
|
| `layout: 'composite'` | Already default | Omit it |
|
|
120
|
-
| Inline style attributes | Scattered styling |
|
|
99
|
+
| Inline style attributes | Scattered styling | PurgeTSS utilities when detected; otherwise TSS |
|
|
121
100
|
| `lib/` prefix | lib/ is flattened | Use path without lib/ |
|
|
122
101
|
| `$.index.open()` | Wrong ID reference | Use actual Window ID |
|
|
123
|
-
| `createNotification` | API doesn't exist |
|
|
102
|
+
| `createNotification` | API doesn't exist | Classify feedback; Snackbar is the transient default |
|
|
124
103
|
| `extendEdges` alone | Content behind nav bar | Add `autoAdjustScrollViewInsets: true` |
|
|
@@ -54,9 +54,11 @@ const name = user.name
|
|
|
54
54
|
|
|
55
55
|
## Styling with TSS
|
|
56
56
|
|
|
57
|
+
First detect `purgetss/` or `purgetss/config.cjs`. If present, invoke the `purgetss` skill, use verified utilities, and do not edit generated `app/styles/app.tss`. The manual TSS organization below applies when PurgeTSS is not present or to specific properties for which that skill confirms no utility.
|
|
58
|
+
|
|
57
59
|
### TSS file organization
|
|
58
60
|
|
|
59
|
-
Every controller can have a matching TSS file. Alloy also supports a global `app.tss`.
|
|
61
|
+
Every controller can have a matching TSS file. Standard Alloy also supports a manually maintained global `app.tss`.
|
|
60
62
|
|
|
61
63
|
```
|
|
62
64
|
app/styles/
|
|
@@ -95,7 +97,7 @@ app/styles/
|
|
|
95
97
|
| Selector | Use for | Example |
|
|
96
98
|
| ------------------- | --------------------------- | ---------------------- |
|
|
97
99
|
| Element (`"Label"`) | App-wide defaults | Base font, color |
|
|
98
|
-
| Class (`".card"`) | Reusable
|
|
100
|
+
| Class (`".card"`) | Reusable visual pattern | Cards, buttons, inputs |
|
|
99
101
|
| ID (`"#name"`) | Unique element positioning | Specific layout needs |
|
|
100
102
|
|
|
101
103
|
### Class selectors in XML
|
|
@@ -127,7 +129,7 @@ When an element has both an ID and classes, properties merge with ID taking prio
|
|
|
127
129
|
<Button id="saveButton" class="btn-primary" title="L('save')" />
|
|
128
130
|
```
|
|
129
131
|
|
|
130
|
-
### Building a design system in app.tss
|
|
132
|
+
### Building a standard Alloy design system in app.tss
|
|
131
133
|
|
|
132
134
|
Define reusable style classes for consistency:
|
|
133
135
|
|
|
@@ -281,43 +281,40 @@ async function loadData() {
|
|
|
281
281
|
|
|
282
282
|
## Modal and dialog patterns
|
|
283
283
|
|
|
284
|
+
Declare the app-owned `dialog` and `bottomSheet` Widgets in the window XML. Attach them after the host exists and destroy them from the controller cleanup. Keep a modal Window for a distinct multi-step task, not for ordinary feedback.
|
|
285
|
+
|
|
284
286
|
```javascript
|
|
285
|
-
|
|
287
|
+
function attachFeedback(hostWindow) {
|
|
288
|
+
$.dialog.attach(hostWindow)
|
|
289
|
+
$.bottomSheet.attach(hostWindow)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Critical, irreversible confirmation
|
|
286
293
|
function showConfirmDelete(itemName) {
|
|
287
|
-
|
|
294
|
+
$.dialog.show({
|
|
288
295
|
title: L('confirm_delete'),
|
|
289
296
|
message: String.format(L('delete_message'), itemName),
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
destructive:
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
dialog.addEventListener('click', (e) => {
|
|
296
|
-
if (e.index === 1) {
|
|
297
|
-
performDelete()
|
|
298
|
-
}
|
|
297
|
+
cancelTitle: L('cancel'),
|
|
298
|
+
confirmTitle: L('delete'),
|
|
299
|
+
destructive: true,
|
|
300
|
+
tone: 'warning',
|
|
301
|
+
onConfirm: performDelete
|
|
299
302
|
})
|
|
300
|
-
|
|
301
|
-
dialog.show()
|
|
302
303
|
}
|
|
303
304
|
|
|
304
|
-
//
|
|
305
|
+
// Contextual choices
|
|
305
306
|
function showSortOptions() {
|
|
306
|
-
const
|
|
307
|
+
const items = [
|
|
308
|
+
{ id: 'name', title: L('sort_name') },
|
|
309
|
+
{ id: 'date', title: L('sort_date') },
|
|
310
|
+
{ id: 'price', title: L('sort_price') }
|
|
311
|
+
]
|
|
307
312
|
|
|
308
|
-
|
|
313
|
+
$.bottomSheet.show({
|
|
309
314
|
title: L('sort_by'),
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
dialog.addEventListener('click', (e) => {
|
|
315
|
-
if (e.index < options.length) {
|
|
316
|
-
applySortOption(e.index)
|
|
317
|
-
}
|
|
315
|
+
items,
|
|
316
|
+
onSelect: item => applySortOption(item.id)
|
|
318
317
|
})
|
|
319
|
-
|
|
320
|
-
dialog.show()
|
|
321
318
|
}
|
|
322
319
|
|
|
323
320
|
// Modal Window
|
|
@@ -341,6 +338,14 @@ function openModal(route, params) {
|
|
|
341
338
|
|
|
342
339
|
return controller
|
|
343
340
|
}
|
|
341
|
+
|
|
342
|
+
function cleanup() {
|
|
343
|
+
$.dialog.destroy()
|
|
344
|
+
$.bottomSheet.destroy()
|
|
345
|
+
$.destroy()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
$.cleanup = cleanup
|
|
344
349
|
```
|
|
345
350
|
|
|
346
351
|
## Tabgroup pattern
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<!-- TOC-START -->
|
|
4
4
|
## Contents
|
|
5
5
|
|
|
6
|
+
- [Reading these examples](#reading-these-examples)
|
|
6
7
|
- [API client service](#api-client-service)
|
|
7
8
|
- [Native module wrapper service](#native-module-wrapper-service)
|
|
8
9
|
- [i18n helper](#i18n-helper)
|
|
@@ -17,6 +18,12 @@
|
|
|
17
18
|
|
|
18
19
|
<!-- TOC-END -->
|
|
19
20
|
|
|
21
|
+
## Reading these examples
|
|
22
|
+
|
|
23
|
+
Snippets that call `$.dialog`, `$.bottomSheet`, or `$.snackbar` expect the app to own those surfaces as Alloy Widgets under `app/widgets/`. Their public API, queueing, and cleanup rules live in [Feedback Widget Contracts](feedback-widget-contracts.md); [Feedback Surfaces](feedback-surfaces.md) covers which surface a given event deserves. The `<Widget>` declaration alone will not compile until those Widgets exist and are registered in `app/config.json`.
|
|
24
|
+
|
|
25
|
+
Styles below carry literal hex values so each example reads on its own. Production code should name semantic color roles instead, so light and dark resolve without a second code path — see [Theming and dark mode](theming.md).
|
|
26
|
+
|
|
20
27
|
## API client service
|
|
21
28
|
Standard logic for network requests.
|
|
22
29
|
|
|
@@ -350,6 +357,8 @@ $.cleanup = cleanup
|
|
|
350
357
|
<ImageView id="emptyIcon" image="/images/search.png" />
|
|
351
358
|
<Label id="emptyLabel" text="L('no_results')" />
|
|
352
359
|
</View>
|
|
360
|
+
|
|
361
|
+
<Widget id="bottomSheet" src="com.example.ui.bottomsheet" />
|
|
353
362
|
</Window>
|
|
354
363
|
</Alloy>
|
|
355
364
|
```
|
|
@@ -375,6 +384,7 @@ let searchQuery = ''
|
|
|
375
384
|
let debounceTimer = null
|
|
376
385
|
|
|
377
386
|
function init() {
|
|
387
|
+
$.bottomSheet.attach($.contactsWindow)
|
|
378
388
|
loadData()
|
|
379
389
|
|
|
380
390
|
$.searchBar.addEventListener('cancel', clearSearch)
|
|
@@ -395,21 +405,23 @@ function clearSearch() {
|
|
|
395
405
|
}
|
|
396
406
|
|
|
397
407
|
function showFilters() {
|
|
398
|
-
const
|
|
408
|
+
const items = [
|
|
409
|
+
{ id: 'all', title: L('all') },
|
|
410
|
+
{ id: 'work', title: L('work') },
|
|
411
|
+
{ id: 'personal', title: L('personal') },
|
|
412
|
+
{ id: 'family', title: L('family') }
|
|
413
|
+
]
|
|
414
|
+
|
|
415
|
+
$.bottomSheet.show({
|
|
399
416
|
title: L('filter_by_category'),
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
dialog.addEventListener('click', (e) => {
|
|
405
|
-
if (e.index < 4) {
|
|
406
|
-
activeFilters.category = e.index === 0 ? null : ['work', 'personal', 'family'][e.index - 1]
|
|
417
|
+
items,
|
|
418
|
+
selectedId: activeFilters.category || 'all',
|
|
419
|
+
onSelect: item => {
|
|
420
|
+
activeFilters.category = item.id === 'all' ? null : item.id
|
|
407
421
|
updateFilterTags()
|
|
408
422
|
applyFilters()
|
|
409
423
|
}
|
|
410
424
|
})
|
|
411
|
-
|
|
412
|
-
dialog.show()
|
|
413
425
|
}
|
|
414
426
|
|
|
415
427
|
function applyFilters() {
|
|
@@ -465,6 +477,7 @@ function createFilterTag(label, onRemove) {
|
|
|
465
477
|
|
|
466
478
|
function cleanup() {
|
|
467
479
|
clearTimeout(debounceTimer)
|
|
480
|
+
$.bottomSheet.destroy()
|
|
468
481
|
$.searchBar.removeEventListener('change', onSearchChange)
|
|
469
482
|
$.destroy()
|
|
470
483
|
}
|
|
@@ -530,6 +543,7 @@ $.cleanup = cleanup
|
|
|
530
543
|
<Switch id="termsSwitch" />
|
|
531
544
|
<Label id="termsLabel" text="L('accept_terms')" />
|
|
532
545
|
</View>
|
|
546
|
+
<Label id="termsError" />
|
|
533
547
|
|
|
534
548
|
<!-- Register Button -->
|
|
535
549
|
<Button id="registerBtn"
|
|
@@ -544,6 +558,8 @@ $.cleanup = cleanup
|
|
|
544
558
|
</View>
|
|
545
559
|
</View>
|
|
546
560
|
</ScrollView>
|
|
561
|
+
|
|
562
|
+
<Widget id="dialog" src="com.example.ui.dialog" />
|
|
547
563
|
</Window>
|
|
548
564
|
</Alloy>
|
|
549
565
|
```
|
|
@@ -570,6 +586,7 @@ $.cleanup = cleanup
|
|
|
570
586
|
"#termsRow": { layout: 'horizontal', left: 16, right: 16, top: 24, width: Ti.UI.FILL }
|
|
571
587
|
"#termsSwitch": { width: 48 }
|
|
572
588
|
"#termsLabel": { left: 8 }
|
|
589
|
+
"#termsError": { left: 16, right: 16, top: 4, font: { fontSize: 12 }, color: '#ef4444', visible: false }
|
|
573
590
|
"#registerBtn": { left: 16, right: 16, top: 24, height: 56, borderRadius: 12, backgroundColor: '#2563eb', color: '#fff', font: { fontWeight: 'bold' } }
|
|
574
591
|
"#loginRow": { layout: 'horizontal', top: 16 }
|
|
575
592
|
"#haveAccountLabel": { font: { fontSize: 14 }, color: '#4b5563' }
|
|
@@ -611,6 +628,8 @@ const validators = {
|
|
|
611
628
|
}
|
|
612
629
|
|
|
613
630
|
function init() {
|
|
631
|
+
$.dialog.attach($.registerWindow)
|
|
632
|
+
|
|
614
633
|
// Field navigation
|
|
615
634
|
$.nameField.addEventListener('return', () => $.emailField.focus())
|
|
616
635
|
$.emailField.addEventListener('return', () => $.passwordField.focus())
|
|
@@ -658,16 +677,14 @@ function validateAll() {
|
|
|
658
677
|
const emailValid = validateField('email')
|
|
659
678
|
const passwordValid = validateField('password')
|
|
660
679
|
const confirmValid = validateField('confirm')
|
|
680
|
+
const termsValid = $.termsSwitch.value
|
|
661
681
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}).show()
|
|
667
|
-
return false
|
|
668
|
-
}
|
|
682
|
+
$.termsError.applyProperties({
|
|
683
|
+
text: termsValid ? '' : L('error_accept_terms'),
|
|
684
|
+
visible: !termsValid
|
|
685
|
+
})
|
|
669
686
|
|
|
670
|
-
return nameValid && emailValid && passwordValid && confirmValid
|
|
687
|
+
return nameValid && emailValid && passwordValid && confirmValid && termsValid
|
|
671
688
|
}
|
|
672
689
|
|
|
673
690
|
async function onRegister() {
|
|
@@ -686,10 +703,12 @@ async function onRegister() {
|
|
|
686
703
|
Navigation.replace('main')
|
|
687
704
|
|
|
688
705
|
} catch (error) {
|
|
689
|
-
|
|
706
|
+
$.dialog.show({
|
|
690
707
|
title: L('error'),
|
|
691
|
-
message: error.message
|
|
692
|
-
|
|
708
|
+
message: error.message,
|
|
709
|
+
tone: 'error',
|
|
710
|
+
confirmTitle: L('close')
|
|
711
|
+
})
|
|
693
712
|
} finally {
|
|
694
713
|
setLoading(false)
|
|
695
714
|
}
|
|
@@ -707,6 +726,7 @@ function goToLogin() {
|
|
|
707
726
|
}
|
|
708
727
|
|
|
709
728
|
function cleanup() {
|
|
729
|
+
$.dialog.destroy()
|
|
710
730
|
$.destroy()
|
|
711
731
|
}
|
|
712
732
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Migrating feedback surfaces
|
|
2
|
+
|
|
3
|
+
Migrate semantics, not proxy names. Replacing every native dialog with the same custom modal preserves the original design problem.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Inventory](#inventory)
|
|
8
|
+
- [Classify before replacing](#classify-before-replacing)
|
|
9
|
+
- [Migrate one window at a time](#migrate-one-window-at-a-time)
|
|
10
|
+
- [Deliberate silence](#deliberate-silence)
|
|
11
|
+
- [Contract tests](#contract-tests)
|
|
12
|
+
- [Delivery verification](#delivery-verification)
|
|
13
|
+
- [When to share Widgets between apps](#when-to-share-widgets-between-apps)
|
|
14
|
+
|
|
15
|
+
## Inventory
|
|
16
|
+
|
|
17
|
+
Search the application for:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
rg -n "createAlertDialog|createOptionDialog|createNotification|Toast|toast|snackbar|overlay" app/
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Also inspect helpers that wrap these calls, XML overlays, controller timers, global events, and callbacks that can present UI indirectly. Record for every occurrence:
|
|
24
|
+
|
|
25
|
+
- trigger and current message;
|
|
26
|
+
- success, information, warning, error, confirmation, or choice;
|
|
27
|
+
- blocking versus non-blocking;
|
|
28
|
+
- reversible versus irreversible;
|
|
29
|
+
- app-owned versus system-owned;
|
|
30
|
+
- callback side effects and cancellation behavior;
|
|
31
|
+
- owning window and cleanup path.
|
|
32
|
+
|
|
33
|
+
Do not implement the replacement until every occurrence has a semantic target from `feedback-surfaces.md`.
|
|
34
|
+
|
|
35
|
+
## Classify before replacing
|
|
36
|
+
|
|
37
|
+
Use these defaults:
|
|
38
|
+
|
|
39
|
+
- ordinary success and undoable actions → Snackbar;
|
|
40
|
+
- recoverable field validation → inline validation;
|
|
41
|
+
- persistent app condition → Banner or inline notice;
|
|
42
|
+
- blocking failure or required acknowledgement → Dialog;
|
|
43
|
+
- uncommon irreversible decision → destructive Dialog;
|
|
44
|
+
- several related actions or app-data choices → Bottom Sheet;
|
|
45
|
+
- long structured content → scrollable sheet or dedicated screen;
|
|
46
|
+
- OS permission, picker, share, date/time, biometric, or purchase flow → keep the system surface.
|
|
47
|
+
|
|
48
|
+
Preserve domain behavior separately from presentation. Services return results or errors; the window/controller that owns visible context chooses the surface. Do not let a service create app-owned UI, especially before the root window exists.
|
|
49
|
+
|
|
50
|
+
## Migrate one window at a time
|
|
51
|
+
|
|
52
|
+
1. Register the required Widget dependencies in `app/config.json`.
|
|
53
|
+
2. Add the Widgets as the final visual children of the window XML.
|
|
54
|
+
3. Attach modal Widgets after the window exists.
|
|
55
|
+
4. Route each old result to its classified surface.
|
|
56
|
+
5. Call Widget `destroy()` from the window close cleanup.
|
|
57
|
+
6. Exercise success, failure, cancel, Back, backdrop, double tap, and consecutive presentation paths.
|
|
58
|
+
7. Remove the old dialog, local overlay, helper, timer, and listener after the migrated window passes verification.
|
|
59
|
+
8. Commit the completed vertical slice before moving to the next window.
|
|
60
|
+
|
|
61
|
+
Do not keep commented legacy implementations. Version control is the rollback mechanism; commented duplicates hide incomplete migrations and stale callbacks.
|
|
62
|
+
|
|
63
|
+
## Deliberate silence
|
|
64
|
+
|
|
65
|
+
Cancellation or an unknowable result can be a complete, intentional outcome. Model it explicitly and do not infer a message from falsy values.
|
|
66
|
+
|
|
67
|
+
Example contract for an exported file:
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
function handleSaveResult(error, result) {
|
|
71
|
+
if (error) {
|
|
72
|
+
$.dialog.show({
|
|
73
|
+
title: L('error'),
|
|
74
|
+
message: error.message,
|
|
75
|
+
tone: 'error',
|
|
76
|
+
confirmTitle: L('close')
|
|
77
|
+
})
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (result.saved === true) {
|
|
82
|
+
$.snackbar.show({
|
|
83
|
+
message: L('file_saved'),
|
|
84
|
+
tone: 'success'
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// saved === false: Android picker was cancelled; the internal original remains.
|
|
89
|
+
// saved === null: iOS does not report whether the person completed the save.
|
|
90
|
+
// Both outcomes are deliberately silent.
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Do not add "Cancelled" for `saved === false`. Do not claim success for `saved === null`. Cover both branches with tests so a future fallback does not erase the intended silence.
|
|
95
|
+
|
|
96
|
+
## Contract tests
|
|
97
|
+
|
|
98
|
+
Add repository-appropriate static or unit tests for:
|
|
99
|
+
|
|
100
|
+
- all Widget folders, manifests, XML views, controllers, and dependency entries;
|
|
101
|
+
- each public method documented in `feedback-widget-contracts.md`;
|
|
102
|
+
- the expected Widget declared in every migrated window;
|
|
103
|
+
- absence of app-owned `Ti.UI.createAlertDialog` and `Ti.UI.createOptionDialog` defaults;
|
|
104
|
+
- removal of the previous local Snackbar/overlay and timer;
|
|
105
|
+
- Snackbar replacement and timer reset;
|
|
106
|
+
- Dialog and Bottom Sheet FIFO ordering;
|
|
107
|
+
- exactly-once callbacks under double taps and competing close paths;
|
|
108
|
+
- Android Back and backdrop policy;
|
|
109
|
+
- cleanup of active and queued presentations;
|
|
110
|
+
- explicit silence for cancel and indeterminate save results.
|
|
111
|
+
|
|
112
|
+
Do not enforce blanket bans on native system surfaces. A remaining native call must be tied to an OS-owned interaction or documented fallback.
|
|
113
|
+
|
|
114
|
+
## Delivery verification
|
|
115
|
+
|
|
116
|
+
Run the project's real commands rather than assuming names. A complete migration normally includes:
|
|
117
|
+
|
|
118
|
+
1. lint with zero errors and no new warnings;
|
|
119
|
+
2. unit and contract tests with no new failures;
|
|
120
|
+
3. PurgeTSS generation when detected, followed by inspection of unused or unsupported classes at the end of `app.tss`;
|
|
121
|
+
4. Alloy compilation for Android and iOS;
|
|
122
|
+
5. native builds for every supported platform;
|
|
123
|
+
6. manual checks in light, dark, and system appearance;
|
|
124
|
+
7. phone and tablet/large-screen checks;
|
|
125
|
+
8. Back, backdrop, cancel, double tap, long list, and long localized text checks;
|
|
126
|
+
9. VoiceOver and TalkBack focus/traversal checks;
|
|
127
|
+
10. consecutive presentation and window-close cleanup checks.
|
|
128
|
+
|
|
129
|
+
Record pre-existing failures before migration and require the final run to add none. Build artifacts are not proof of usable modal behavior; perform the manual interaction checks on device or simulator.
|
|
130
|
+
|
|
131
|
+
## When to share Widgets between apps
|
|
132
|
+
|
|
133
|
+
Extract or copy a Widget into another app only when:
|
|
134
|
+
|
|
135
|
+
- its API is stable and contains no business rules;
|
|
136
|
+
- localized strings and callbacks are supplied by the host;
|
|
137
|
+
- appearance depends on a documented semantic-token contract;
|
|
138
|
+
- platform and minimum Titanium/Alloy versions are declared;
|
|
139
|
+
- lifecycle and accessibility tests travel with it;
|
|
140
|
+
- at least two apps need the same behavior, or one app has a clear portability requirement.
|
|
141
|
+
|
|
142
|
+
Do not extract merely because a view appeared three times. Extract because the boundary is independently understandable, testable, and maintainable.
|
|
143
|
+
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Feedback surfaces and app-owned UI
|
|
2
|
+
|
|
3
|
+
Use this guide before choosing a Titanium proxy or drawing a component. The surface follows the meaning of the event: style comes afterwards.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Decision sequence](#decision-sequence)
|
|
8
|
+
- [Feedback scale](#feedback-scale)
|
|
9
|
+
- [App-owned versus system-owned](#app-owned-versus-system-owned)
|
|
10
|
+
- [What to standardize next](#what-to-standardize-next)
|
|
11
|
+
- [Phone, tablet, and accessibility](#phone-tablet-and-accessibility)
|
|
12
|
+
- [References](#references)
|
|
13
|
+
|
|
14
|
+
## Decision sequence
|
|
15
|
+
|
|
16
|
+
Classify the event in this order:
|
|
17
|
+
|
|
18
|
+
1. **Owner**: does the app own the interaction, or does the operating system?
|
|
19
|
+
2. **Blocking**: can the person continue safely without acknowledging it?
|
|
20
|
+
3. **Recovery**: is the action reversible or is data at risk?
|
|
21
|
+
4. **Location**: does the message belong to a field, a screen, or the whole app?
|
|
22
|
+
5. **Persistence**: is it transient, persistent until state changes, or modal?
|
|
23
|
+
6. **Choice shape**: is there one acknowledgement, a binary decision, or a list of related actions?
|
|
24
|
+
|
|
25
|
+
Do not begin with "which native dialog can display this string?" That turns different meanings into the same interruption and makes the app feel visually inconsistent.
|
|
26
|
+
|
|
27
|
+
## Feedback scale
|
|
28
|
+
|
|
29
|
+
| Situation | Preferred surface | Why |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| Ordinary success or confirmation | Snackbar | Visible without interrupting the task |
|
|
32
|
+
| Undoable action | Snackbar with one Undo action | Keeps recovery close to the event |
|
|
33
|
+
| Non-blocking warning | Warning Snackbar | Calls attention without forcing acknowledgement |
|
|
34
|
+
| Invalid value in one field | Inline validation next to that field | Preserves context and identifies the fix |
|
|
35
|
+
| Form-wide recoverable validation | Inline summary; Snackbar only if no single field owns it | Avoids a modal loop |
|
|
36
|
+
| Persistent connectivity, sync, or account state | Banner or inline notice | Remains visible while the condition remains true |
|
|
37
|
+
| Blocking error or required acknowledgement | Dialog | The task cannot continue safely |
|
|
38
|
+
| Uncommon irreversible action | Destructive confirmation Dialog | Provides a safe escape before data loss |
|
|
39
|
+
| Several actions related to the current context | Bottom Sheet | Presents a compact choice without pretending it is an error |
|
|
40
|
+
| Long, searchable, or multi-select options | Bottom Sheet, sheet, or dedicated screen with `ListView` | A dialog is too constrained |
|
|
41
|
+
| Multi-step task or complex form | Sheet or dedicated screen | The interaction has its own workflow |
|
|
42
|
+
| No content yet | Empty State inside the screen | This is screen state, not transient feedback |
|
|
43
|
+
| Initial or blocking work | Loading/Progress state in context | Communicates progress without inventing a message |
|
|
44
|
+
|
|
45
|
+
Reserve Dialogs for critical information. Do not use them merely because they are easy to create. If an action is common and undoable, prefer immediate execution plus Undo over confirmation.
|
|
46
|
+
|
|
47
|
+
### Snackbar versus Toast
|
|
48
|
+
|
|
49
|
+
For app-owned foreground feedback, standardize on Snackbar. It supports the app's semantic colors, accessibility, safe-area positioning, duration policy, and one optional recovery action. Do not maintain both a custom Toast and a Snackbar for the same job.
|
|
50
|
+
|
|
51
|
+
A platform Toast can remain only when the operating system owns the behavior or when a documented platform fallback is necessary. It is not the default design surface.
|
|
52
|
+
|
|
53
|
+
### Banner versus Snackbar
|
|
54
|
+
|
|
55
|
+
- Use Snackbar when the event has happened and the message can disappear.
|
|
56
|
+
- Use Banner or inline notice while an ongoing condition remains relevant.
|
|
57
|
+
- Never keep a Snackbar indefinitely to simulate persistent screen state.
|
|
58
|
+
|
|
59
|
+
### Bottom Sheet versus Dialog
|
|
60
|
+
|
|
61
|
+
- Use Bottom Sheet for related choices: sorting, exporting, selecting a category, or choosing an action for an item.
|
|
62
|
+
- Use Dialog for acknowledgement or a consequential confirm/cancel decision.
|
|
63
|
+
- Move long prose, structured data, search, or complex input into a sheet or dedicated screen.
|
|
64
|
+
|
|
65
|
+
## App-owned versus system-owned
|
|
66
|
+
|
|
67
|
+
Recreate and brand a surface only when the app owns its content and behavior. Keep the platform UI when the system owns trust, permissions, identity, files, or another protected workflow.
|
|
68
|
+
|
|
69
|
+
| Keep native/system-owned | May be app-owned and styled |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| Permission prompts | Success, warning, and error feedback |
|
|
72
|
+
| File and document pickers | Destructive confirmations |
|
|
73
|
+
| Share sheets and activity views | Contextual action lists |
|
|
74
|
+
| Date/time pickers when the platform interaction is desired | Category and app-data selectors |
|
|
75
|
+
| Camera and media authorization UI | Loading, empty, and error states |
|
|
76
|
+
| Biometric authentication prompt | Banners and inline validation |
|
|
77
|
+
| Keyboard, autofill, purchases, notifications, and OS settings | App-specific onboarding or education |
|
|
78
|
+
|
|
79
|
+
An app may explain why a system surface is about to appear, but it must not imitate the trusted system prompt. Use the official Titanium API or native module for the system-owned part.
|
|
80
|
+
|
|
81
|
+
Cancellation is often a normal system outcome. Do not turn every cancellation into an error, a "Cancelled" message, or an unsupported success claim. Document silence explicitly at the call site when it is meaningful.
|
|
82
|
+
|
|
83
|
+
## What to standardize next
|
|
84
|
+
|
|
85
|
+
After Snackbar, Dialog, and Bottom Sheet, the highest-value shared patterns are:
|
|
86
|
+
|
|
87
|
+
1. **Banner / Inline Notice** for persistent app state.
|
|
88
|
+
2. **Loading / Progress wrapper** that uses the native activity indicator but standardizes placement, text, blocking behavior, and cleanup.
|
|
89
|
+
3. **Empty / Error State** for consistent screen-level recovery.
|
|
90
|
+
4. **Field Validation** for message placement, focus, accessibility, and colors.
|
|
91
|
+
|
|
92
|
+
Standardize buttons, cards, badges, chips, skeletons, and coach marks only when real product repetition exposes a stable contract. A visual resemblance alone does not justify a Widget.
|
|
93
|
+
|
|
94
|
+
## Phone, tablet, and accessibility
|
|
95
|
+
|
|
96
|
+
- On phones, present contextual choices from the bottom and keep touch targets reachable.
|
|
97
|
+
- On tablets and large screens, use a centered, width-limited action surface or a platform-appropriate anchored presentation; do not stretch phone geometry edge to edge.
|
|
98
|
+
- Respect safe areas, bottom navigation, orientation, split screen, text scaling, and localized strings.
|
|
99
|
+
- Announce transient feedback appropriately and move accessibility focus into a modal surface when it opens.
|
|
100
|
+
- While a modal is active, prevent interaction with and accessibility traversal into the underlying content. Restore the exact previous state on close.
|
|
101
|
+
- Back, backdrop, Cancel, and destructive actions must have predictable semantics and must never execute callbacks more than once.
|
|
102
|
+
|
|
103
|
+
## References
|
|
104
|
+
|
|
105
|
+
- [Titanium Alloy Widgets](https://titaniumsdk.com/guide/Alloy_Framework/Alloy_Guide/Alloy_Widgets.html)
|
|
106
|
+
- [Apple Human Interface Guidelines: Alerts](https://developer.apple.com/design/human-interface-guidelines/alerts)
|
|
107
|
+
- [Apple Human Interface Guidelines: Modality](https://developer.apple.com/design/human-interface-guidelines/modality)
|
|
108
|
+
- [Material Design 3: Snackbar](https://m3.material.io/components/snackbar/overview)
|
|
109
|
+
- [Material Design 3: Dialogs](https://m3.material.io/components/dialogs/overview)
|
|
110
|
+
- [Material Design 3: Bottom sheets](https://m3.material.io/components/bottom-sheets/overview)
|
|
111
|
+
|