@maccesar/titools 2.2.12 → 2.4.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 +9 -1
- package/lib/cache.js +49 -0
- package/lib/commands/auto-update.js +138 -0
- package/lib/commands/skills.js +11 -0
- package/lib/commands/uninstall.js +17 -1
- package/lib/commands/update.js +5 -6
- package/lib/config.js +3 -0
- package/lib/downloader.js +10 -0
- package/lib/hooks.js +59 -0
- package/lib/utils.js +1 -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 +46 -45
- 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/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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ti-expert
|
|
3
|
-
description: "Titanium SDK architecture and implementation expert. Use when designing, reviewing, analyzing, or examining Titanium project structure (Alloy or Classic), creating controllers/views/services, choosing models vs collections, implementing communication patterns, handling memory cleanup, testing, auditing code,
|
|
3
|
+
description: "Titanium SDK architecture and implementation expert. Use when designing, reviewing, analyzing, or examining Titanium project structure (Alloy or Classic), creating controllers/views/services, choosing models vs collections, implementing communication patterns, handling memory cleanup, testing, auditing code, migrating legacy apps, or building adaptive/responsive layouts for tablets, foldables, and large screens. Automatically identifies project type."
|
|
4
4
|
argument-hint: "[architecture-topic]"
|
|
5
5
|
allowed-tools: Read, Grep, Glob, Edit, Write, Bash(git *), Bash(node *)
|
|
6
6
|
---
|
|
@@ -11,23 +11,22 @@ Practical architecture and implementation guidance for Titanium SDK apps (Alloy
|
|
|
11
11
|
|
|
12
12
|
## Project detection
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
This skill detects project type automatically and tailors guidance.
|
|
16
|
-
|
|
17
|
-
Alloy indicators:
|
|
18
|
-
- `app/` folder (MVC structure)
|
|
19
|
-
- `app/views/`, `app/controllers/` folders
|
|
20
|
-
- `alloy.jmk` or `config.json` files
|
|
21
|
-
|
|
22
|
-
Classic indicators:
|
|
23
|
-
- `Resources/` folder with `app.js` at root
|
|
24
|
-
- No `app/` folder structure
|
|
25
|
-
|
|
26
|
-
Behavior:
|
|
27
|
-
- Alloy detected: provides Alloy MVC patterns and Backbone.js guidance
|
|
28
|
-
- Classic detected: avoids Alloy-only patterns and recommends Classic options or migration
|
|
29
|
-
- Unknown: asks the user to clarify the project type
|
|
30
|
-
:::
|
|
14
|
+
> **️ℹ️ Auto-detects Alloy vs Classic projects**
|
|
15
|
+
> This skill detects project type automatically and tailors guidance.
|
|
16
|
+
>
|
|
17
|
+
> Alloy indicators:
|
|
18
|
+
> - `app/` folder (MVC structure)
|
|
19
|
+
> - `app/views/`, `app/controllers/` folders
|
|
20
|
+
> - `alloy.jmk` or `config.json` files
|
|
21
|
+
>
|
|
22
|
+
> Classic indicators:
|
|
23
|
+
> - `Resources/` folder with `app.js` at root
|
|
24
|
+
> - No `app/` folder structure
|
|
25
|
+
>
|
|
26
|
+
> Behavior:
|
|
27
|
+
> - Alloy detected: provides Alloy MVC patterns and Backbone.js guidance
|
|
28
|
+
> - Classic detected: avoids Alloy-only patterns and recommends Classic options or migration
|
|
29
|
+
> - Unknown: asks the user to clarify the project type
|
|
31
30
|
|
|
32
31
|
## Workflow
|
|
33
32
|
|
|
@@ -76,6 +75,8 @@ Detailed examples and full implementation samples are available in: [Architectur
|
|
|
76
75
|
|
|
77
76
|
- No semicolons: let ASI handle it
|
|
78
77
|
- Modern syntax: `const/let`, destructuring, template literals
|
|
78
|
+
- Prefer stable property ordering in JS objects and `applyProperties()` calls
|
|
79
|
+
- Default to alphabetical property order when it does not break a meaningful semantic grouping
|
|
79
80
|
- `applyProperties()`: batch UI updates to reduce bridge crossings
|
|
80
81
|
- Memory cleanup: any controller with global listeners must set `$.cleanup = cleanup`
|
|
81
82
|
- Error handling: use AppError classes, log with context, never swallow errors
|
|
@@ -83,33 +84,32 @@ Detailed examples and full implementation samples are available in: [Architectur
|
|
|
83
84
|
|
|
84
85
|
## Titanium style sheets rules (low freedom)
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
Using `Ti.UI.iOS.*` or `Ti.UI.Android.*` properties without platform modifiers breaks cross-platform builds.
|
|
88
|
-
|
|
89
|
-
Example of the damage:
|
|
90
|
-
```tss
|
|
91
|
-
// Wrong: adds Ti.UI.iOS to Android project
|
|
92
|
-
"#mainWindow": {
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Correct: always use platform modifiers
|
|
98
|
-
```tss
|
|
99
|
-
// Correct: only adds to iOS
|
|
100
|
-
"#mainWindow[platform=ios]": {
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
Properties that always need platform modifiers:
|
|
106
|
-
- iOS: `statusBarStyle`, `modalStyle`, `modalTransitionStyle`, any `Ti.UI.iOS.*`
|
|
107
|
-
- Android: `actionBar` configuration, any `Ti.UI.Android.*` constant
|
|
108
|
-
|
|
109
|
-
Available modifiers: `[platform=ios]`, `[platform=android]`, `[formFactor=handheld]`, `[formFactor=tablet]`, `[if=Alloy.Globals.customVar]`
|
|
110
|
-
|
|
111
|
-
For more platform-specific patterns, see the `ti-ui` skill.
|
|
112
|
-
:::
|
|
87
|
+
> **🚨 Critical: platform-specific properties require modifiers**
|
|
88
|
+
> Using `Ti.UI.iOS.*` or `Ti.UI.Android.*` properties without platform modifiers breaks cross-platform builds.
|
|
89
|
+
>
|
|
90
|
+
> Example of the damage:
|
|
91
|
+
> ```tss
|
|
92
|
+
> // Wrong: adds Ti.UI.iOS to Android project
|
|
93
|
+
> "#mainWindow": {
|
|
94
|
+
> statusBarStyle: Ti.UI.iOS.StatusBar.LIGHT_CONTENT
|
|
95
|
+
> }
|
|
96
|
+
> ```
|
|
97
|
+
>
|
|
98
|
+
> Correct: always use platform modifiers
|
|
99
|
+
> ```tss
|
|
100
|
+
> // Correct: only adds to iOS
|
|
101
|
+
> "#mainWindow[platform=ios]": {
|
|
102
|
+
> statusBarStyle: Ti.UI.iOS.StatusBar.LIGHT_CONTENT
|
|
103
|
+
> }
|
|
104
|
+
> ```
|
|
105
|
+
>
|
|
106
|
+
> Properties that always need platform modifiers:
|
|
107
|
+
> - iOS: `statusBarStyle`, `modalStyle`, `modalTransitionStyle`, any `Ti.UI.iOS.*`
|
|
108
|
+
> - Android: `actionBar` configuration, any `Ti.UI.Android.*` constant
|
|
109
|
+
>
|
|
110
|
+
> Available modifiers: `[platform=ios]`, `[platform=android]`, `[formFactor=handheld]`, `[formFactor=tablet]`, `[if=Alloy.Globals.customVar]`
|
|
111
|
+
>
|
|
112
|
+
> For more platform-specific patterns, see the `ti-ui` skill.
|
|
113
113
|
|
|
114
114
|
Titanium layout system:
|
|
115
115
|
- Three layout modes: `layout: 'horizontal'`, `layout: 'vertical'`, and composite (default, no `layout` needed)
|
|
@@ -149,6 +149,7 @@ For the complete reference with examples, see [Alloy builtins and globals](refer
|
|
|
149
149
|
- [Contracts & Communication](references/contracts.md)
|
|
150
150
|
- [State Management](references/state-management.md)
|
|
151
151
|
- [Anti-patterns to Avoid](references/anti-patterns.md)
|
|
152
|
+
- [Adaptive Layouts](references/adaptive-layouts.md) (responsive breakpoints, tablet/foldable/desktop support, Android 17 resizability compliance)
|
|
152
153
|
|
|
153
154
|
### Implementation & API
|
|
154
155
|
- [Alloy Builtins & Globals](references/alloy-builtins.md)
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# Adaptive layouts
|
|
2
|
+
|
|
3
|
+
Patterns for building responsive Titanium apps that work across phones, tablets, foldables, and desktop mode.
|
|
4
|
+
|
|
5
|
+
This reference is NOT derived from official Titanium documentation. It addresses Google's Android 17 resizability requirements and provides reusable patterns for adaptive UI in Titanium/Alloy apps.
|
|
6
|
+
|
|
7
|
+
## 1. Context: Android 17 resizability requirements
|
|
8
|
+
|
|
9
|
+
Starting with Android 17 (API 37), on devices with screens >= 600dp (tablets, foldables, desktop mode), Google ignores these manifest attributes:
|
|
10
|
+
|
|
11
|
+
- `android:screenOrientation` (all values, including `portrait`)
|
|
12
|
+
- `setRequestedOrientation()`
|
|
13
|
+
- `android:resizeableActivity`
|
|
14
|
+
- `android:minAspectRatio` / `android:maxAspectRatio`
|
|
15
|
+
|
|
16
|
+
Deadline: August 2027 — Google Play requires `targetSdkVersion="37"` for all new apps and updates.
|
|
17
|
+
|
|
18
|
+
Scope: Only affects screens >= 600dp. Phones (< 600dp) and games (`android:appCategory="game"`) are exempt.
|
|
19
|
+
|
|
20
|
+
What this means: portrait-only apps will be forced into landscape/resized on large screens. If the UI doesn't support it, it will break.
|
|
21
|
+
|
|
22
|
+
### Related: edge-to-edge (API 36)
|
|
23
|
+
|
|
24
|
+
Before the resizability requirement (API 37), Android SDK 36 removes the opt-out for edge-to-edge rendering, causing Titanium layouts to overlap system navigation bars. The Titanium core team has a PR to handle this at the SDK level: tidev/titanium-sdk#14399 — adds an `EdgeToEdgeHelper` with correct padding so existing apps work without code changes.
|
|
25
|
+
|
|
26
|
+
| Android requirement | API level | Who handles it |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| Edge-to-edge (system bars) | SDK 36 | Titanium SDK (PR #14399) |
|
|
29
|
+
| Resizability / orientation unlock | SDK 37 | App developer (this reference) |
|
|
30
|
+
|
|
31
|
+
### Full requirements checklist
|
|
32
|
+
|
|
33
|
+
| Requirement | Ti.UI.FILL/SIZE solves it? | What to do in Titanium |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| No orientation lock on >= 600dp | Yes | Remove the lock from tiapp.xml |
|
|
36
|
+
| Multi-window / split-screen | Yes | Avoid fixed dimensions |
|
|
37
|
+
| Variable aspect ratios | Yes | Use FILL/SIZE/percentages |
|
|
38
|
+
| Preserve state on config changes | **Handled by Titanium** | Titanium declares `android:configChanges` on all Activities automatically — Android does NOT destroy/recreate them on rotation, resize, or split-screen. No extra code needed. |
|
|
39
|
+
| External input (keyboard, mouse) | **No** | Handle `keypressed` events, ensure touch targets work with mouse clicks |
|
|
40
|
+
|
|
41
|
+
For most apps, removing the portrait lock and using `Ti.UI.FILL`/`Ti.UI.SIZE` correctly is sufficient. External input is only needed for apps targeting desktop mode or connected displays.
|
|
42
|
+
|
|
43
|
+
### Two levels of adaptive layout
|
|
44
|
+
|
|
45
|
+
**Level 1 — Fluid layout (most apps):** Use `Ti.UI.FILL`, `Ti.UI.SIZE`, and percentages. No JS code needed. The layout engine adapts automatically. With PurgeTSS: `w-screen`, `h-auto`, and percentage utilities.
|
|
46
|
+
|
|
47
|
+
**Level 2 — Breakpoint-driven layout (structural changes):** Use the responsive helper + `postlayout` event + layout adapters when the structure must change (e.g., sidebar lateral on tablet, hidden on phone). The rest of this reference covers Level 2 patterns.
|
|
48
|
+
|
|
49
|
+
## 2. Detecting window size
|
|
50
|
+
|
|
51
|
+
### platformWidth vs win.size
|
|
52
|
+
|
|
53
|
+
There are two ways to get dimensions, and they behave differently:
|
|
54
|
+
|
|
55
|
+
| API | Returns | In split-screen |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `Ti.Platform.displayCaps.platformWidth` | Full device screen width (pixels on Android) | Always returns full device width — WRONG for layout decisions |
|
|
58
|
+
| `$.win.size.width` (after `postlayout`) | Actual window width in app's default unit | Returns the real available width — CORRECT for layout decisions |
|
|
59
|
+
|
|
60
|
+
Google explicitly recommends: "do not use device screen size for layout decisions — use the actual window size." In native Android this is `WindowManager.getCurrentWindowMetrics()`. In Titanium, the equivalent is `win.size` after `postlayout`.
|
|
61
|
+
|
|
62
|
+
### Units: dp is the default in Titanium
|
|
63
|
+
|
|
64
|
+
Titanium uses `dp` as the default unit. The `win.size` property returns values in the app's default unit. Since the default is dp, `win.size.width` already returns dp values — do NOT divide by `logicalDensityFactor` or you will get a double-conversion (halved values).
|
|
65
|
+
|
|
66
|
+
`platformWidth` is different: it returns absolute pixels on Android regardless of `ti.ui.defaultunit`. This requires dividing by `logicalDensityFactor` to convert to dp.
|
|
67
|
+
|
|
68
|
+
### Responsive helper — `app/lib/responsive.js`
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
/**
|
|
72
|
+
* Detects breakpoints using the actual window size.
|
|
73
|
+
* Accurate in split-screen, multi-window, and freeform modes.
|
|
74
|
+
*
|
|
75
|
+
* win.size returns dp values (Titanium default unit is dp).
|
|
76
|
+
* Do NOT divide by logicalDensityFactor — that would double-convert.
|
|
77
|
+
*/
|
|
78
|
+
const getInfoFromWindow = (winSize) => {
|
|
79
|
+
const dpWidth = winSize.width
|
|
80
|
+
const dpHeight = winSize.height
|
|
81
|
+
|
|
82
|
+
const isPortrait = dpHeight > dpWidth
|
|
83
|
+
const isTablet = Math.min(dpWidth, dpHeight) >= 600
|
|
84
|
+
|
|
85
|
+
let breakpoint = 'phone'
|
|
86
|
+
if (dpWidth >= 900) {
|
|
87
|
+
breakpoint = 'desktop'
|
|
88
|
+
} else if (dpWidth >= 600) {
|
|
89
|
+
breakpoint = 'tablet'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { dpWidth, dpHeight, isPortrait, isTablet, breakpoint }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Fallback: detects breakpoints using device screen size.
|
|
97
|
+
* Use only when win.size is not available (e.g., before postlayout).
|
|
98
|
+
* NOT accurate in split-screen — returns full device width.
|
|
99
|
+
*
|
|
100
|
+
* platformWidth returns pixels on Android — must convert to dp.
|
|
101
|
+
*/
|
|
102
|
+
const getInfoFromDevice = () => {
|
|
103
|
+
const { platformWidth: w, platformHeight: h, logicalDensityFactor: density } = Ti.Platform.displayCaps
|
|
104
|
+
|
|
105
|
+
const isPortrait = Ti.Gesture.portrait
|
|
106
|
+
const factor = OS_ANDROID ? density : 1
|
|
107
|
+
const dpWidth = (isPortrait ? Math.min(w, h) : Math.max(w, h)) / factor
|
|
108
|
+
const dpHeight = (isPortrait ? Math.max(w, h) : Math.min(w, h)) / factor
|
|
109
|
+
const isTablet = Math.min(w, h) / factor >= 600
|
|
110
|
+
|
|
111
|
+
let breakpoint = 'phone'
|
|
112
|
+
if (dpWidth >= 900) {
|
|
113
|
+
breakpoint = 'desktop'
|
|
114
|
+
} else if (dpWidth >= 600) {
|
|
115
|
+
breakpoint = 'tablet'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { dpWidth, dpHeight, isPortrait, isTablet, breakpoint }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const onChange = (callback) => {
|
|
122
|
+
const handler = () => {
|
|
123
|
+
callback(getInfoFromDevice())
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
Ti.Gesture.addEventListener('orientationchange', handler)
|
|
127
|
+
return handler
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const removeOff = (handler) => {
|
|
131
|
+
Ti.Gesture.removeEventListener('orientationchange', handler)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = { getInfoFromWindow, getInfoFromDevice, onChange, removeOff }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Quick inline detection
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
// After postlayout — accurate in split-screen
|
|
141
|
+
const dpWidth = $.win.size.width // already dp, no conversion needed
|
|
142
|
+
const isTablet = Math.min($.win.size.width, $.win.size.height) >= 600
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 3. Layout strategy
|
|
146
|
+
|
|
147
|
+
| Breakpoint | dpWidth | Strategy |
|
|
148
|
+
| ---------- | --------- | ------------------------------------- |
|
|
149
|
+
| Phone | < 600dp | Vertical stack, sidebar hidden |
|
|
150
|
+
| Tablet | 600-899dp | Side-by-side split (30/70 or 40/60) |
|
|
151
|
+
| Desktop | >= 900dp | Fixed or percentage sidebar + fluid content |
|
|
152
|
+
|
|
153
|
+
## 4. Controller pattern
|
|
154
|
+
|
|
155
|
+
### View — `app/views/example.xml`
|
|
156
|
+
|
|
157
|
+
```xml
|
|
158
|
+
<Alloy>
|
|
159
|
+
<Window id="win" backgroundColor="#fff">
|
|
160
|
+
<View id="container">
|
|
161
|
+
<View id="sidebar" />
|
|
162
|
+
<View id="content" />
|
|
163
|
+
</View>
|
|
164
|
+
</Window>
|
|
165
|
+
</Alloy>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Styles — `app/styles/example.tss`
|
|
169
|
+
|
|
170
|
+
```tss
|
|
171
|
+
"#container": {
|
|
172
|
+
width: Ti.UI.FILL,
|
|
173
|
+
height: Ti.UI.FILL
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
"#sidebar": {
|
|
177
|
+
backgroundColor: "#eee",
|
|
178
|
+
layout: "vertical"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
"#content": {
|
|
182
|
+
backgroundColor: "#fff",
|
|
183
|
+
layout: "vertical"
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Controller — `app/controllers/example.js`
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
const Responsive = require('/responsive')
|
|
191
|
+
|
|
192
|
+
let state = Responsive.getInfoFromDevice()
|
|
193
|
+
let postlayoutHandler
|
|
194
|
+
|
|
195
|
+
// --- Layout adapters ---
|
|
196
|
+
|
|
197
|
+
function layoutPhone() {
|
|
198
|
+
$.container.layout = 'vertical'
|
|
199
|
+
|
|
200
|
+
$.sidebar.visible = false
|
|
201
|
+
|
|
202
|
+
$.content.applyProperties({
|
|
203
|
+
height: Ti.UI.FILL,
|
|
204
|
+
width: Ti.UI.FILL
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function layoutTablet() {
|
|
209
|
+
$.container.layout = 'horizontal'
|
|
210
|
+
|
|
211
|
+
$.sidebar.applyProperties({
|
|
212
|
+
height: Ti.UI.FILL,
|
|
213
|
+
visible: true,
|
|
214
|
+
width: '30%'
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
$.content.applyProperties({
|
|
218
|
+
height: Ti.UI.FILL,
|
|
219
|
+
width: '70%'
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function layoutDesktop() {
|
|
224
|
+
$.container.layout = 'horizontal'
|
|
225
|
+
|
|
226
|
+
$.sidebar.applyProperties({
|
|
227
|
+
height: Ti.UI.FILL,
|
|
228
|
+
visible: true,
|
|
229
|
+
width: 280
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
$.content.applyProperties({
|
|
233
|
+
height: Ti.UI.FILL,
|
|
234
|
+
width: Ti.UI.FILL
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// --- Apply layout based on breakpoint ---
|
|
239
|
+
|
|
240
|
+
function applyLayout() {
|
|
241
|
+
switch (state.breakpoint) {
|
|
242
|
+
case 'phone':
|
|
243
|
+
layoutPhone()
|
|
244
|
+
break
|
|
245
|
+
case 'tablet':
|
|
246
|
+
layoutTablet()
|
|
247
|
+
break
|
|
248
|
+
case 'desktop':
|
|
249
|
+
layoutDesktop()
|
|
250
|
+
break
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// --- Lifecycle ---
|
|
255
|
+
|
|
256
|
+
// Use postlayout to detect actual window size.
|
|
257
|
+
// This is accurate in split-screen where platformWidth is not.
|
|
258
|
+
postlayoutHandler = () => {
|
|
259
|
+
const newState = Responsive.getInfoFromWindow($.win.size)
|
|
260
|
+
const changed = newState.breakpoint !== state.breakpoint
|
|
261
|
+
|
|
262
|
+
state = newState
|
|
263
|
+
|
|
264
|
+
// Only re-apply layout when breakpoint changes.
|
|
265
|
+
// Percentage-based widths adapt automatically via the layout engine.
|
|
266
|
+
// Re-applying the same layout causes visible double-adjustment flicker.
|
|
267
|
+
if (changed) {
|
|
268
|
+
applyLayout()
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
$.win.addEventListener('postlayout', postlayoutHandler)
|
|
273
|
+
|
|
274
|
+
applyLayout()
|
|
275
|
+
|
|
276
|
+
// --- Memory cleanup (mandatory for controllers with global listeners) ---
|
|
277
|
+
|
|
278
|
+
function cleanup() {
|
|
279
|
+
if (postlayoutHandler) {
|
|
280
|
+
$.win.removeEventListener('postlayout', postlayoutHandler)
|
|
281
|
+
postlayoutHandler = null
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
$.win.addEventListener('close', cleanup)
|
|
286
|
+
$.cleanup = cleanup
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## 5. Grid simulation
|
|
290
|
+
|
|
291
|
+
Titanium does NOT have a `gap` property. Use `left` / `top` offsets in horizontal/vertical layouts to simulate spacing between siblings.
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
function createRow() {
|
|
295
|
+
return Ti.UI.createView({
|
|
296
|
+
layout: 'horizontal',
|
|
297
|
+
height: Ti.UI.SIZE,
|
|
298
|
+
width: Ti.UI.FILL
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function createCol(percent, spacing) {
|
|
303
|
+
return Ti.UI.createView({
|
|
304
|
+
width: percent + '%',
|
|
305
|
+
height: Ti.UI.SIZE,
|
|
306
|
+
left: spacing || 0
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Usage in a tablet layout:
|
|
311
|
+
function layoutTabletGrid() {
|
|
312
|
+
$.content.removeAllChildren()
|
|
313
|
+
|
|
314
|
+
const row = createRow()
|
|
315
|
+
|
|
316
|
+
const col1 = createCol(48)
|
|
317
|
+
col1.add(Ti.UI.createLabel({ text: 'Left column' }))
|
|
318
|
+
|
|
319
|
+
// 4% gap simulated via left offset
|
|
320
|
+
const col2 = createCol(48, '4%')
|
|
321
|
+
col2.add(Ti.UI.createLabel({ text: 'Right column' }))
|
|
322
|
+
|
|
323
|
+
row.add(col1)
|
|
324
|
+
row.add(col2)
|
|
325
|
+
|
|
326
|
+
$.content.add(row)
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## 6. Anti-patterns
|
|
331
|
+
|
|
332
|
+
```javascript
|
|
333
|
+
// ❌ Using platformWidth for layout decisions in split-screen
|
|
334
|
+
// Returns full device width, not the app's window width
|
|
335
|
+
const dpWidth = Ti.Platform.displayCaps.platformWidth / density
|
|
336
|
+
// Use $.win.size.width after postlayout instead
|
|
337
|
+
|
|
338
|
+
// ❌ Dividing win.size by logicalDensityFactor
|
|
339
|
+
// win.size already returns dp (Titanium's default unit) — double-conversion!
|
|
340
|
+
const dpWidth = $.win.size.width / density // WRONG: halves the value
|
|
341
|
+
|
|
342
|
+
// ❌ Using Ti.Gesture.isPortrait() — not a method, it's a property
|
|
343
|
+
Ti.Gesture.isPortrait() // TypeError!
|
|
344
|
+
Ti.Gesture.portrait // correct — boolean property
|
|
345
|
+
|
|
346
|
+
// ❌ Using gap property — does NOT exist in Titanium
|
|
347
|
+
{ layout: 'horizontal', gap: 10 }
|
|
348
|
+
|
|
349
|
+
// ❌ Anonymous listeners on global objects — memory leak, can never be removed
|
|
350
|
+
Ti.Gesture.addEventListener('orientationchange', () => { ... })
|
|
351
|
+
|
|
352
|
+
// ❌ Re-applying layout when breakpoint hasn't changed — causes flicker
|
|
353
|
+
// Percentage-based widths already adapt via the layout engine
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## 7. tiapp.xml configuration
|
|
357
|
+
|
|
358
|
+
### Android: enable multiple orientations
|
|
359
|
+
|
|
360
|
+
The `<activity>` element must include `android:name` and be nested inside `<application>`. Without `android:name`, Android's manifest merger fails with "Missing 'name' key attribute on element activity".
|
|
361
|
+
|
|
362
|
+
Titanium generates the main activity name as `<app-id>.<Appname>Activity`. For example, app ID `com.example.myapp` with name `MyApp` produces `com.example.myapp.MyappActivity`.
|
|
363
|
+
|
|
364
|
+
```xml
|
|
365
|
+
<android xmlns:android="http://schemas.android.com/apk/res/android">
|
|
366
|
+
<manifest>
|
|
367
|
+
<application>
|
|
368
|
+
<activity
|
|
369
|
+
android:name="com.example.myapp.MyappActivity"
|
|
370
|
+
android:screenOrientation="fullSensor"/>
|
|
371
|
+
</application>
|
|
372
|
+
</manifest>
|
|
373
|
+
</android>
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
To verify the exact generated activity name, check `build/android/AndroidManifest.xml` after a build.
|
|
377
|
+
|
|
378
|
+
### iOS: support all orientations on iPad
|
|
379
|
+
|
|
380
|
+
```xml
|
|
381
|
+
<ios>
|
|
382
|
+
<plist>
|
|
383
|
+
<dict>
|
|
384
|
+
<key>UISupportedInterfaceOrientations</key>
|
|
385
|
+
<array>
|
|
386
|
+
<string>UIInterfaceOrientationPortrait</string>
|
|
387
|
+
</array>
|
|
388
|
+
<key>UISupportedInterfaceOrientations~ipad</key>
|
|
389
|
+
<array>
|
|
390
|
+
<string>UIInterfaceOrientationPortrait</string>
|
|
391
|
+
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
392
|
+
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
393
|
+
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
394
|
+
</array>
|
|
395
|
+
</dict>
|
|
396
|
+
</plist>
|
|
397
|
+
</ios>
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## 8. Quick reference
|
|
401
|
+
|
|
402
|
+
| Concept | Titanium API |
|
|
403
|
+
| --- | --- |
|
|
404
|
+
| Window size (split-screen safe) | `$.win.size.width` / `$.win.size.height` after `postlayout` — already in dp |
|
|
405
|
+
| Device screen size | `Ti.Platform.displayCaps.platformWidth` — pixels on Android, needs `/logicalDensityFactor` |
|
|
406
|
+
| Orientation (boolean) | `Ti.Gesture.portrait`, `Ti.Gesture.landscape` — properties, not methods |
|
|
407
|
+
| Orientation event | `Ti.Gesture.addEventListener('orientationchange', handler)` |
|
|
408
|
+
| Window resize event | `$.win.addEventListener('postlayout', handler)` — fires on rotation, split-screen, resize |
|
|
409
|
+
| Auto-size | `Ti.UI.SIZE` (fit content), `Ti.UI.FILL` (fill remaining) |
|
|
410
|
+
| Percentages | `width: '50%'` relative to parent |
|
|
411
|
+
| Spacing | `left`/`top` as offset from previous sibling in horizontal/vertical |
|
|
412
|
+
| Batch updates | `view.applyProperties({...})` to reduce bridge overhead |
|
|
413
|
+
|
|
414
|
+
For detailed layout APIs, see the `ti-ui` skill's [layouts-and-positioning.md](../../ti-ui/references/layouts-and-positioning.md) and [orientation.md](../../ti-ui/references/orientation.md).
|
|
@@ -23,12 +23,11 @@ if (OS_ANDROID) {
|
|
|
23
23
|
}
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
`OS_IOS` and `OS_ANDROID` are replaced by the Alloy compiler with literal `true`/`false`. The opposite platform's code is completely removed from the build output. This is different from `Ti.Platform.osname === 'iphone'` which remains in the code.
|
|
28
|
-
|
|
29
|
-
**Use for:** Platform-specific APIs, platform-specific UI configuration
|
|
30
|
-
**Do NOT use for:** Logic that should be testable on both platforms
|
|
31
|
-
:::
|
|
26
|
+
> **⚠️ These are NOT runtime checks**
|
|
27
|
+
> `OS_IOS` and `OS_ANDROID` are replaced by the Alloy compiler with literal `true`/`false`. The opposite platform's code is completely removed from the build output. This is different from `Ti.Platform.osname === 'iphone'` which remains in the code.
|
|
28
|
+
>
|
|
29
|
+
> **Use for:** Platform-specific APIs, platform-specific UI configuration
|
|
30
|
+
> **Do NOT use for:** Logic that should be testable on both platforms
|
|
32
31
|
|
|
33
32
|
### Env_dev / env_test / env_production
|
|
34
33
|
|
|
@@ -132,9 +131,8 @@ const debug = Alloy.CFG.debug // true in dev, false in prod
|
|
|
132
131
|
const appName = Alloy.CFG.appName // From global
|
|
133
132
|
```
|
|
134
133
|
|
|
135
|
-
|
|
136
|
-
`Alloy.CFG` values are set at build time and cannot be modified at runtime. For runtime configuration, use `Alloy.Globals` or `Ti.App.Properties`.
|
|
137
|
-
:::
|
|
134
|
+
> **💡 CFG is read-only**
|
|
135
|
+
> `Alloy.CFG` values are set at build time and cannot be modified at runtime. For runtime configuration, use `Alloy.Globals` or `Ti.App.Properties`.
|
|
138
136
|
|
|
139
137
|
---
|
|
140
138
|
|
|
@@ -256,14 +254,13 @@ const title = $.args.title || ''
|
|
|
256
254
|
const showBack = $.args.showBack !== 'false' // XML passes strings
|
|
257
255
|
```
|
|
258
256
|
|
|
259
|
-
|
|
260
|
-
When passing values through XML attributes, they arrive as strings in `$.args`. Numbers and booleans need to be parsed:
|
|
261
|
-
```javascript
|
|
262
|
-
const count = parseInt($.args.count, 10) // "5" → 5
|
|
263
|
-
const visible = $.args.visible !== 'false' // "false" → false
|
|
264
|
-
```
|
|
265
|
-
For complex data, pass via `Alloy.createController()` in the parent controller instead.
|
|
266
|
-
:::
|
|
257
|
+
> **⚠️ XML attributes are always strings**
|
|
258
|
+
> When passing values through XML attributes, they arrive as strings in `$.args`. Numbers and booleans need to be parsed:
|
|
259
|
+
> ```javascript
|
|
260
|
+
> const count = parseInt($.args.count, 10) // "5" → 5
|
|
261
|
+
> const visible = $.args.visible !== 'false' // "false" → false
|
|
262
|
+
> ```
|
|
263
|
+
> For complex data, pass via `Alloy.createController()` in the parent controller instead.
|
|
267
264
|
|
|
268
265
|
---
|
|
269
266
|
|
|
@@ -365,9 +362,8 @@ users.fetch({
|
|
|
365
362
|
</View>
|
|
366
363
|
```
|
|
367
364
|
|
|
368
|
-
|
|
369
|
-
The `if` attribute is checked when the controller is created. It does NOT react to later changes in the condition. For dynamic visibility, use the `visible` property in the controller.
|
|
370
|
-
:::
|
|
365
|
+
> **⚠️ `if` is evaluated once at controller creation**
|
|
366
|
+
> The `if` attribute is checked when the controller is created. It does NOT react to later changes in the condition. For dynamic visibility, use the `visible` property in the controller.
|
|
371
367
|
|
|
372
368
|
---
|
|
373
369
|
|