@maccesar/titools 5.1.0 → 5.2.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 +44 -12
- package/agents/ti-pro.md +4 -0
- package/commands/ti-audit.md +4 -0
- package/lib/config.js +2 -0
- package/package.json +1 -1
- package/skills/purgetss/SKILL.md +4 -4
- package/skills/purgetss/references/EXAMPLES.md +3 -3
- package/skills/purgetss/references/adopting-purgetss.md +2 -2
- package/skills/purgetss/references/animation-advanced.md +2 -2
- package/skills/purgetss/references/arbitrary-values.md +8 -5
- package/skills/purgetss/references/class-categories.md +5 -5
- package/skills/purgetss/references/class-index-properties.md +6 -6
- package/skills/purgetss/references/class-index.md +2 -2
- package/skills/purgetss/references/cli-commands.md +2 -2
- package/skills/purgetss/references/dynamic-component-creation.md +2 -2
- package/skills/purgetss/references/grid-layout.md +1 -1
- package/skills/purgetss/references/migration-guide.md +34 -4
- package/skills/purgetss/references/multi-density-images.md +6 -6
- package/skills/purgetss/references/platform-modifiers.md +21 -0
- package/skills/purgetss/references/ui-ux-design.md +1 -1
- package/skills/purgetss/references/version-history.md +9 -4
- package/skills/ti-admob-unified/SKILL.md +44 -0
- package/skills/ti-admob-unified/agents/openai.yaml +4 -0
- package/skills/ti-admob-unified/assets/example/app.js +1003 -0
- package/skills/ti-admob-unified/assets/example/lib/ads.js +618 -0
- package/skills/ti-admob-unified/assets/example/library-usage.js +104 -0
- package/skills/ti-admob-unified/references/api-reference.md +592 -0
- package/skills/ti-admob-unified/references/banners.md +435 -0
- package/skills/ti-admob-unified/references/contributing.md +228 -0
- package/skills/ti-admob-unified/references/dependencies.md +85 -0
- package/skills/ti-admob-unified/references/fullscreen.md +223 -0
- package/skills/ti-admob-unified/references/game-monetization.md +17 -0
- package/skills/ti-admob-unified/references/getting-started.md +275 -0
- package/skills/ti-admob-unified/references/index.md +30 -0
- package/skills/ti-admob-unified/references/integration.md +85 -0
- package/skills/ti-admob-unified/references/library.md +189 -0
- package/skills/ti-admob-unified/references/mediation.md +132 -0
- package/skills/ti-admob-unified/references/migration.md +255 -0
- package/skills/ti-admob-unified/references/module-changelog.md +54 -0
- package/skills/ti-admob-unified/references/module-license.txt +21 -0
- package/skills/ti-admob-unified/references/privacy.md +254 -0
- package/skills/ti-admob-unified/references/roadmap.md +146 -0
- package/skills/ti-admob-unified/references/source-notes.md +33 -0
- package/skills/ti-admob-unified/references/source-snapshot.json +440 -0
- package/skills/ti-admob-unified/references/third-party-notices.md +22 -0
- package/skills/ti-game/SKILL.md +10 -1
- package/skills/ti-game/references/api.md +10 -2
- package/skills/ti-game/references/debugging-performance.md +6 -0
- package/skills/ti-game/references/examples.md +11 -1
- package/skills/ti-game/references/patterns.md +24 -2
- package/skills/ti-game/references/physics.md +502 -0
- package/skills/ti-game/references/project-setup.md +23 -1
- package/skills/ti-game/references/recipes-action.md +46 -14
- package/skills/ti-game/references/recipes-adventure-effects.md +12 -1
- package/skills/ti-game/references/roadmap.md +2 -2
- package/skills/ti-game/references/tilemaps.md +2 -1
- package/skills/ti-game-assets/SKILL.md +53 -0
- package/skills/ti-game-assets/agents/openai.yaml +4 -0
- package/skills/ti-game-assets/references/alpha.md +26 -0
- package/skills/ti-game-assets/references/art-direction.md +37 -0
- package/skills/ti-game-assets/references/integration.md +49 -0
- package/skills/ti-game-assets/references/manifest.example.json +429 -0
- package/skills/ti-game-assets/references/manifest.md +39 -0
- package/skills/ti-game-assets/references/offline-audio.md +33 -0
- package/skills/ti-game-assets/references/prompts.md +33 -0
- package/skills/ti-game-assets/references/repeating-textures.md +17 -0
- package/skills/ti-game-assets/references/responsible-ads.md +31 -0
- package/skills/ti-game-assets/references/sound-assets.md +42 -0
- package/skills/ti-game-assets/references/vector-sources.md +21 -0
- package/skills/ti-game-assets/scripts/alpha_tools.py +160 -0
- package/skills/ti-game-assets/scripts/requirements.txt +1 -0
- package/skills/ti-game-assets/scripts/validate_manifest.py +267 -0
- package/skills/ti-synthengine/SKILL.md +37 -13
- package/skills/ti-synthengine/references/api.md +397 -31
- package/skills/ti-synthengine/references/audio-engine.md +235 -14
- package/skills/ti-synthengine/references/examples.md +396 -17
- package/skills/ti-synthengine/references/guide.md +331 -12
- package/skills/ti-synthengine/references/recipes.md +212 -22
- package/skills/ti-synthengine/references/recommendations.md +53 -16
- package/skills/ti-synthengine/references/sound-design.md +359 -15
- package/skills/ti-synthengine/references/source-notes.md +121 -0
- package/skills/ti-synthengine/references/source-snapshot.json +105 -0
- package/skills/ti-synthengine/references/stats-performance.json +2992 -0
- package/skills/ti-synthengine/references/stats-performance.md +38 -0
- package/skills/ti-synthengine/references/troubleshooting.md +61 -4
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
> Source snapshot: official ti.admob.unified 1.0.0 documentation at commit `5b4cbf9` (copied 2026-10-11). Relative links rebased for this skill.
|
|
2
|
+
|
|
3
|
+
# Banner ads
|
|
4
|
+
|
|
5
|
+
Standard and adaptive banners share one public lifecycle and event contract on iOS and Android.
|
|
6
|
+
|
|
7
|
+
Both factories return a Titanium `TiViewProxy`, so their creation dictionaries also accept normal Titanium view/layout properties such as `width`, `height`, `top`, `bottom`, `left`, and `right`. The tables below list the AdMob-specific properties.
|
|
8
|
+
|
|
9
|
+
<!-- TOC-START -->
|
|
10
|
+
## Contents
|
|
11
|
+
|
|
12
|
+
- [Choose a banner type](#choose-a-banner-type)
|
|
13
|
+
- [Adaptive banner](#adaptive-banner)
|
|
14
|
+
- [Reserve real layout space](#reserve-real-layout-space)
|
|
15
|
+
- [Runtime properties](#runtime-properties)
|
|
16
|
+
- [Shared methods](#shared-methods)
|
|
17
|
+
- [Events and payloads](#events-and-payloads)
|
|
18
|
+
- [Standard banner](#standard-banner)
|
|
19
|
+
- [Refresh behavior](#refresh-behavior)
|
|
20
|
+
|
|
21
|
+
<!-- TOC-END -->
|
|
22
|
+
|
|
23
|
+
## Choose a banner type
|
|
24
|
+
|
|
25
|
+
Use `createAdaptiveBanner()` for a banner anchored outside scrolling content. Its height is calculated from the available width, orientation, and device.
|
|
26
|
+
|
|
27
|
+
Use `createBanner()` when the placement explicitly requires a fixed Google ad size such as `BANNER`, `MEDIUM_RECTANGLE`, or `LEADERBOARD`.
|
|
28
|
+
|
|
29
|
+
| Capability | iOS | Android |
|
|
30
|
+
| ------------------------------ | --- | ------- |
|
|
31
|
+
| Standard fixed-size banner | Yes | Yes |
|
|
32
|
+
| Large anchored adaptive banner | Yes | Yes |
|
|
33
|
+
| Inline adaptive banner | Yes | Yes |
|
|
34
|
+
| Collapsible anchored banner | Yes | Yes |
|
|
35
|
+
| Dynamic loaded height | Yes | Yes |
|
|
36
|
+
|
|
37
|
+
## Adaptive banner
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
const banner = Admob.createAdaptiveBanner({
|
|
41
|
+
adUnitId: Admob.TEST_ADAPTIVE_BANNER,
|
|
42
|
+
adaptiveType: Admob.ADAPTIVE_ANCHORED,
|
|
43
|
+
width: Ti.UI.FILL
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
bannerHost.add(banner)
|
|
47
|
+
banner.load()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The proxy must be in the Titanium view hierarchy before `load()` so its native view can measure the real container width.
|
|
51
|
+
|
|
52
|
+
### Creation properties
|
|
53
|
+
|
|
54
|
+
| Property | Type | iOS | Android | Notes |
|
|
55
|
+
| -------------- | -------- | ----------: | ----------: | ------------------------------------------------------------ |
|
|
56
|
+
| `adUnitId` | String | Yes | Yes | Required. |
|
|
57
|
+
| `adaptiveType` | String | Yes | Yes | Defaults to `ADAPTIVE_ANCHORED`. |
|
|
58
|
+
| `maxHeight` | Number | Inline only | Inline only | Maximum inline height in dp; both platforms default to `50`. |
|
|
59
|
+
| `collapsible` | String | Yes | Yes | Native placement string: `"top"` or `"bottom"`. |
|
|
60
|
+
| `keywords` | String[] | Yes | Yes | Prefer contextual configuration in AdMob. |
|
|
61
|
+
| `contentUrl` | String | Yes | Yes | URL describing surrounding content. |
|
|
62
|
+
| `requestAgent` | String | Yes | Yes | Identifies what issued the request. |
|
|
63
|
+
| `extras` | Object | Yes | Yes | Google adapter request extras. |
|
|
64
|
+
|
|
65
|
+
On iOS, anchored adaptive banners use the large size because Google deprecated the plain anchored functions. Large banners are 50 to 150 points tall and never exceed 20% of the screen; the deprecated size capped at 90 points and 15%. For a shorter fixed strip, use `createBanner()` with `customAdSize`.
|
|
66
|
+
|
|
67
|
+
Events echo back the constant you passed, so `event.adaptiveType` can be compared against `ADAPTIVE_ANCHORED` or `ADAPTIVE_INLINE` on either platform.
|
|
68
|
+
|
|
69
|
+
`ADAPTIVE_INLINE` and `maxHeight` work on both platforms. Inline banners scroll with the content and use the height limit you supply. Anchored banners sit at a screen edge and use the height Google calculates; `maxHeight` has no effect on them.
|
|
70
|
+
|
|
71
|
+
`collapsible` takes Google's native placement string, `"top"` and `"bottom"`, on both platforms. The module exports no `COLLAPSIBLE_TOP` or `COLLAPSIBLE_BOTTOM` constant.
|
|
72
|
+
|
|
73
|
+
Both platforms send the option under the same ad-network extra key, using a Bundle on Android and `GADExtras` on iOS. No separate collapsible ad-unit setting is required. A request may return a standard banner, and mediated ads are non-collapsible. Fixed and adaptive banners include `isCollapsible` in `AD_LOADED`, copied from the SDK for the received ad. This reports the ad classification, not its expanded state or visible rendering. Calling `load()` requests another ad; it does not expand an existing creative.
|
|
74
|
+
|
|
75
|
+
Google's current requirements are described in the collapsible banner guide, published per platform: [Android](https://developers.google.com/admob/android/banner/collapsible), [iOS](https://developers.google.com/admob/ios/banner/collapsible).
|
|
76
|
+
|
|
77
|
+
## Reserve real layout space
|
|
78
|
+
|
|
79
|
+
The loaded height uses density-independent units on both platforms:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
banner.addEventListener(Admob.AD_LOADED, event => {
|
|
83
|
+
bannerHost.height = `${event.height}dp`
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Do not hardcode the final height. Adaptive banners can change height across devices, densities, widths, and orientations. On iOS, calculate the placement height with `Admob.getAnchoredAdaptiveBannerSize(widthInDp)` before loading and wait for a nonzero `postlayout` event on the banner. Replace the reservation with `event.height` after loading. Requesting an ad in a zero-height host can suppress its initial collapsible expansion even when `isCollapsible` is true. This reservation applies to large anchored banners, not inline banners.
|
|
88
|
+
|
|
89
|
+
A safe composite layout reserves the same bottom inset for content:
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
const root = Ti.UI.createView({
|
|
93
|
+
layout: 'composite'
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const content = Ti.UI.createView({
|
|
97
|
+
top: 0,
|
|
98
|
+
left: 0,
|
|
99
|
+
right: 0,
|
|
100
|
+
bottom: 0
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const bannerHost = Ti.UI.createView({
|
|
104
|
+
left: 0,
|
|
105
|
+
right: 0,
|
|
106
|
+
bottom: 0,
|
|
107
|
+
height: 0
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
root.add(content)
|
|
111
|
+
root.add(bannerHost)
|
|
112
|
+
|
|
113
|
+
function setBannerHeight(value) {
|
|
114
|
+
const height = Math.max(0, Number(value) || 0)
|
|
115
|
+
const dimension = `${height}dp`
|
|
116
|
+
|
|
117
|
+
bannerHost.height = dimension
|
|
118
|
+
content.bottom = dimension
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This keeps the banner inside the visible layout and prevents it from overlapping lists, navigation, snackbars, or other bottom-aligned UI. Place `root` inside the app's safe-area-aware content region.
|
|
123
|
+
|
|
124
|
+
Do not use a vertical layout where a preceding `Ti.UI.FILL` child consumes the entire height and pushes the banner host below the window.
|
|
125
|
+
|
|
126
|
+
### Full lifecycle example
|
|
127
|
+
|
|
128
|
+
Use named listeners so they can be removed:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
let banner = null
|
|
132
|
+
const isIOS = Ti.Platform.osname !== 'android'
|
|
133
|
+
|
|
134
|
+
function onInitialLayout() {
|
|
135
|
+
if (!banner || banner.rect.width <= 0 || banner.rect.height <= 0) return
|
|
136
|
+
banner.removeEventListener('postlayout', onInitialLayout)
|
|
137
|
+
banner.load()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function createBanner() {
|
|
141
|
+
if (banner || !Admob.canRequestAds()) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
banner = Admob.createAdaptiveBanner({
|
|
146
|
+
adUnitId: Admob.TEST_ADAPTIVE_BANNER,
|
|
147
|
+
adaptiveType: Admob.ADAPTIVE_ANCHORED,
|
|
148
|
+
...(isIOS ? { height: Ti.UI.FILL } : {}),
|
|
149
|
+
width: Ti.UI.FILL
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
banner.addEventListener(Admob.AD_LOADED, onLoaded)
|
|
153
|
+
banner.addEventListener(Admob.AD_FAILED_TO_LOAD, onFailed)
|
|
154
|
+
banner.addEventListener(Admob.AD_DESTROYED, onDestroyed)
|
|
155
|
+
|
|
156
|
+
if (isIOS) {
|
|
157
|
+
setBannerHeight(Admob.getAnchoredAdaptiveBannerSize(bannerHost.rect.width).height)
|
|
158
|
+
banner.addEventListener('postlayout', onInitialLayout)
|
|
159
|
+
bannerHost.add(banner)
|
|
160
|
+
} else {
|
|
161
|
+
bannerHost.add(banner)
|
|
162
|
+
banner.load()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function onLoaded(event) {
|
|
167
|
+
setBannerHeight(event.height)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function onFailed(event) {
|
|
171
|
+
Ti.API.warn(`Banner failed: ${event.error} (${event.code})`)
|
|
172
|
+
setBannerHeight(0)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function onDestroyed() {
|
|
176
|
+
setBannerHeight(0)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function pauseBanner() {
|
|
180
|
+
banner?.pause()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function resumeBanner() {
|
|
184
|
+
banner?.resume()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function destroyBanner() {
|
|
188
|
+
if (!banner) {
|
|
189
|
+
setBannerHeight(0)
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
banner.removeEventListener('postlayout', onInitialLayout)
|
|
194
|
+
banner.removeEventListener(Admob.AD_LOADED, onLoaded)
|
|
195
|
+
banner.removeEventListener(Admob.AD_FAILED_TO_LOAD, onFailed)
|
|
196
|
+
banner.removeEventListener(Admob.AD_DESTROYED, onDestroyed)
|
|
197
|
+
banner.destroy()
|
|
198
|
+
|
|
199
|
+
if (banner.parent) {
|
|
200
|
+
banner.parent.remove(banner)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
banner = null
|
|
204
|
+
setBannerHeight(0)
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Call `pauseBanner()` when the screen that owns the placement stops being shown, `resumeBanner()` when it comes back, and `destroyBanner()` when the placement or window closes.
|
|
209
|
+
|
|
210
|
+
### Who pauses a banner
|
|
211
|
+
|
|
212
|
+
Two independent causes can hold a banner paused, and the module tracks them separately:
|
|
213
|
+
|
|
214
|
+
| Cause | Trigger | Platform |
|
|
215
|
+
| ----------- | -------------------------------------------------------- | --------------- |
|
|
216
|
+
| Application | your `pause()` / `resume()` calls | Both |
|
|
217
|
+
| Lifecycle | activity `onPause`/`onResume`, app background/foreground | Both, automatic |
|
|
218
|
+
|
|
219
|
+
Each pause cause must clear independently. A banner paused because its tab is hidden stays paused when the app returns from the background. A lifecycle pause lasts until the app is visible again, even if you call `resume()` in the meantime. Any deferred load waits for both causes to clear.
|
|
220
|
+
|
|
221
|
+
Android follows the hosting activity, and iOS observes application background/foreground. A secondary Android window backed by another activity needs no manual pause/resume wiring. Use the public methods when navigation hides a placement while its host lifecycle remains active, such as a custom overlay within the same activity. iOS uses background/foreground transitions because opening Control Center or receiving a call banner can resign active while the ad remains visible.
|
|
222
|
+
|
|
223
|
+
When Android recreates an activity for a configuration change, such as light/dark appearance, the module releases the stale native `AdView` and keeps the JavaScript proxy alive. It requests a replacement banner after Titanium creates the new view, without emitting `destroyed`. The creative cannot move between activities because an Android `View` belongs to the activity that created it.
|
|
224
|
+
|
|
225
|
+
## Runtime properties
|
|
226
|
+
|
|
227
|
+
Both platforms expose banner creation properties for reading and writing at runtime. Changes take effect at different points:
|
|
228
|
+
|
|
229
|
+
`keywords`, `contentUrl`, `requestAgent`, `extras` and `collapsible` apply on the next `load()`. They leave the current creative unchanged:
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
banner.extras = { collapsible: 'bottom' }
|
|
233
|
+
banner.requestAgent = 'my-app/2.1'
|
|
234
|
+
banner.load() // now they travel
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Changing `adSize` or `customAdSize` applies on the next `load()` on both platforms. The existing creative remains until that request; the module recreates the native view when needed.
|
|
238
|
+
|
|
239
|
+
Changing `adUnitId` also applies on the next load. The module recreates the native view instead of assigning a second ID to an existing Android AdView, which Google forbids.
|
|
240
|
+
|
|
241
|
+
### What Android writes back
|
|
242
|
+
|
|
243
|
+
Android overwrites the proxy's own `height` when the ad size is known, and the two banner types write different types into it:
|
|
244
|
+
|
|
245
|
+
| Banner | What Android assigns | Example |
|
|
246
|
+
| -------- | -------------------- | -------- |
|
|
247
|
+
| Standard | A number, in dp | `50` |
|
|
248
|
+
| Adaptive | A string, in dp | `"64dp"` |
|
|
249
|
+
|
|
250
|
+
iOS assigns neither value. When reading `banner.height`, handle both a number and a `"…dp"` string. Alternatively, read `event.height` from the `loaded` payload, where it is numeric on both platforms.
|
|
251
|
+
|
|
252
|
+
Android also treats `top`, `left` and `right` as native padding on the `AdView`, on top of the Titanium layout meaning. iOS applies only the Titanium layout.
|
|
253
|
+
|
|
254
|
+
## Shared methods
|
|
255
|
+
|
|
256
|
+
### `load()`
|
|
257
|
+
|
|
258
|
+
Starts an ad request.
|
|
259
|
+
|
|
260
|
+
- A second call is ignored while a request is active.
|
|
261
|
+
- A call while paused defers at most one request until `resume()`.
|
|
262
|
+
- A call after `destroy()` cannot recreate the proxy and emits `failed_to_load`.
|
|
263
|
+
|
|
264
|
+
### `pause()`
|
|
265
|
+
|
|
266
|
+
Idempotently records an application-owned pause. It does not change `visible`, `opacity`, height, or its host, and it does not replace the native proxy or loaded creative.
|
|
267
|
+
|
|
268
|
+
Google's SDKs use different native pause mechanisms:
|
|
269
|
+
|
|
270
|
+
| Behavior | iOS | Android |
|
|
271
|
+
| ------------------------------ | ------------------------------- | ------------------------- |
|
|
272
|
+
| Changes visual state or layout | No | No |
|
|
273
|
+
| Suspends ad-view processing | No equivalent API | Yes, via `AdView.pause()` |
|
|
274
|
+
| Stops native autoload directly | Yes, via `autoloadEnabled = NO` | No direct refresh switch |
|
|
275
|
+
|
|
276
|
+
On Android, `AdView.pause()` pauses extra processing; Google does not expose it as a refresh-control API. Automatic refresh separately requires the banner to be visible. Call `pause()` when another opaque window covers the placement or its tab is deselected, keeping the proxy's visual properties unchanged. A controller state change alone is not a reason to pause a banner the user can still see.
|
|
277
|
+
|
|
278
|
+
On iOS, the module stores the banner's existing `autoloadEnabled` value and sets it to `NO`. This prevents native autoload while leaving the same view and creative intact.
|
|
279
|
+
|
|
280
|
+
A `load()` requested while either pause cause is active waits until both clear. The module can defer the request without changing visibility.
|
|
281
|
+
|
|
282
|
+
Do not destroy/recreate to simulate a pause. That discards the creative, creates a new request on return, and makes ordinary navigation visibly slower.
|
|
283
|
+
|
|
284
|
+
### `resume()`
|
|
285
|
+
|
|
286
|
+
Idempotently resumes the banner and runs one deferred load, if present, but only once every pause cause has cleared. See [Who pauses a banner](#who-pauses-a-banner).
|
|
287
|
+
|
|
288
|
+
It reverses only the native pause: Android resumes ad-view processing and iOS restores the `autoloadEnabled` value the banner had before pausing. There is no visibility or layout transition to undo. Because application lifecycle is tracked separately, `resume()` cannot accidentally clear a background pause.
|
|
289
|
+
|
|
290
|
+
### `isLoaded()`
|
|
291
|
+
|
|
292
|
+
Returns `true` only while the current proxy owns a successfully loaded ad.
|
|
293
|
+
|
|
294
|
+
### `destroy()`
|
|
295
|
+
|
|
296
|
+
Terminal and safe to repeat. It releases the native ad, detaches native delegates/listeners and lifecycle registrations, and emits `destroyed` once.
|
|
297
|
+
|
|
298
|
+
Never reuse a destroyed banner. Create a new proxy for a new placement or a new width/orientation request.
|
|
299
|
+
|
|
300
|
+
### Compatibility aliases
|
|
301
|
+
|
|
302
|
+
`destroyAdViewAndCancelRequest()` delegates to `destroy()`.
|
|
303
|
+
|
|
304
|
+
`destoyAdViewAndCancelRequest()` is the deprecated, misspelled alias retained for older consumers. New code must call `destroy()`.
|
|
305
|
+
|
|
306
|
+
## Events and payloads
|
|
307
|
+
|
|
308
|
+
Both banner types emit:
|
|
309
|
+
|
|
310
|
+
- `AD_LOADED` / `loaded`
|
|
311
|
+
- `AD_FAILED_TO_LOAD` / `failed_to_load`
|
|
312
|
+
- `AD_CLICKED` / `clicked`
|
|
313
|
+
- `AD_IMPRESSION` / `impression`
|
|
314
|
+
- `AD_OPENED` / `opened`
|
|
315
|
+
- `AD_CLOSED` / `closed`
|
|
316
|
+
- `AD_DESTROYED` / `destroyed`
|
|
317
|
+
|
|
318
|
+
Every payload includes `adUnitId`, `adType: 'banner'`, and `isAdaptive`, which reports which factory created the proxy: `true` from `createAdaptiveBanner()`, `false` from `createBanner()`.
|
|
319
|
+
|
|
320
|
+
```js
|
|
321
|
+
{
|
|
322
|
+
adUnitId: 'ca-app-pub-…/…',
|
|
323
|
+
adType: 'banner',
|
|
324
|
+
isAdaptive: false
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Adaptive payloads also include `adaptiveType`.
|
|
329
|
+
|
|
330
|
+
For both banner types, `AD_LOADED` also includes `isCollapsible: boolean`, reporting the native SDK's classification of the received ad. It is not an expanded/collapsed state flag.
|
|
331
|
+
|
|
332
|
+
A failure adds:
|
|
333
|
+
|
|
334
|
+
```js
|
|
335
|
+
{
|
|
336
|
+
error: 'Native error message',
|
|
337
|
+
code: 3
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
An adaptive `loaded` event guarantees:
|
|
342
|
+
|
|
343
|
+
```js
|
|
344
|
+
{
|
|
345
|
+
height: 90,
|
|
346
|
+
heightUnit: 'dp'
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Android additionally reports diagnostic size fields:
|
|
351
|
+
|
|
352
|
+
```js
|
|
353
|
+
{
|
|
354
|
+
width: 392,
|
|
355
|
+
widthUnit: 'dp',
|
|
356
|
+
widthPx: 1080,
|
|
357
|
+
heightPx: 253,
|
|
358
|
+
density: 2.75
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
All seven size fields are present on both platforms. Android density can be fractional; iOS reports the screen scale.
|
|
363
|
+
|
|
364
|
+
## Standard banner
|
|
365
|
+
|
|
366
|
+
```js
|
|
367
|
+
const banner = Admob.createBanner({
|
|
368
|
+
adUnitId: Admob.TEST_BANNER,
|
|
369
|
+
adSize: Admob.BANNER,
|
|
370
|
+
width: Ti.UI.FILL
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
bannerHost.add(banner)
|
|
374
|
+
banner.load()
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Creation properties
|
|
378
|
+
|
|
379
|
+
| Property | Type | iOS | Android | Notes |
|
|
380
|
+
| -------------- | -------- | ---: | ------: | -------------------------------------------------------------------------------- |
|
|
381
|
+
| `adUnitId` | String | Yes | Yes | Required. |
|
|
382
|
+
| `adSize` | String | Yes | Yes | Defaults to `BANNER`. |
|
|
383
|
+
| `customAdSize` | Object | Yes | Yes | `{ width, height }` in dp. Wins over `adSize`. Ignored unless both are positive. |
|
|
384
|
+
| `keywords` | String[] | Yes | Yes | Request keywords. |
|
|
385
|
+
| `contentUrl` | String | Yes | Yes | Surrounding content URL. |
|
|
386
|
+
| `requestAgent` | String | Yes | Yes | Identifies what issued the request. |
|
|
387
|
+
| `extras` | Object | Yes | Yes | Google adapter extras. |
|
|
388
|
+
|
|
389
|
+
`customAdSize` builds `new AdSize(width, height)` on Android and `GADAdSizeFromCGSize` on iOS, so one object produces the same request on both.
|
|
390
|
+
|
|
391
|
+
Both dimensions use density-independent units. `Ti.Platform.displayCaps.platformWidth` returns pixels on Android and points on iOS. Divide by `logicalDensityFactor` only on Android; on a 3x iPhone that division would request a banner one third of the screen width.
|
|
392
|
+
|
|
393
|
+
```js
|
|
394
|
+
function screenWidthInDp() {
|
|
395
|
+
const caps = Ti.Platform.displayCaps
|
|
396
|
+
|
|
397
|
+
if (Ti.Platform.osname === 'android') {
|
|
398
|
+
return Math.round(caps.platformWidth / caps.logicalDensityFactor)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return Math.round(caps.platformWidth)
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
The application must convert Titanium dimensions to the expected units; the module passes both numbers through unchanged. It applies the size when creating the native view. As with `adSize`, later assignments apply on the next load.
|
|
406
|
+
|
|
407
|
+
Choose a custom size your ad units can serve. A larger slot does not add available inventory.
|
|
408
|
+
|
|
409
|
+
Portable fixed sizes, with the device each one was designed for and the height its host view must reserve:
|
|
410
|
+
|
|
411
|
+
| Constant | Size | Intended device |
|
|
412
|
+
| ------------------ | ------- | --------------- |
|
|
413
|
+
| `BANNER` | 320×50 | Phone |
|
|
414
|
+
| `LARGE_BANNER` | 320×100 | Phone |
|
|
415
|
+
| `MEDIUM_RECTANGLE` | 300×250 | Tablet |
|
|
416
|
+
| `FULLBANNER` | 468×60 | Tablet |
|
|
417
|
+
| `LEADERBOARD` | 728×90 | Tablet |
|
|
418
|
+
|
|
419
|
+
These device categories come from Google's `GADAdSize.h`. A phone is 360–430 dp wide, so a 468 dp `FULLBANNER` cannot fit at full size regardless of the host height. Use `BANNER` for the fixed phone size, or `customAdSize` with the screen width or an adaptive banner for a full-width strip.
|
|
420
|
+
|
|
421
|
+
Give the host the requested height. A `FULLBANNER` in a 50 dp box will be clipped.
|
|
422
|
+
|
|
423
|
+
Unknown values, including `smart_banner`, fall back to `BANNER`. Google removed smart banners on Android and deprecated them on iOS. The shared fallback avoids selecting a fixed 320×50 banner on one platform and a full-width strip on the other. For full width, use `customAdSize` or `createAdaptiveBanner()` to let Google choose the height.
|
|
424
|
+
|
|
425
|
+
Changing `adSize` on an existing iOS native banner is not supported. Destroy and recreate the proxy.
|
|
426
|
+
|
|
427
|
+
## Refresh behavior
|
|
428
|
+
|
|
429
|
+
Do not add a JavaScript renewal timer. Google Mobile Ads respects the banner refresh rate configured for the ad unit in AdMob. The application requests the initial load and lets the SDK/ad-unit configuration own later refreshes.
|
|
430
|
+
|
|
431
|
+
TiAdMobUnified manages the native banner proxy and its lifecycle without a periodic reload or recreation loop. Google documents that Android automatic refresh requires a visible banner. Set or disable the refresh interval in AdMob, and use screen-level `pause()`/`resume()` when navigation hides or reveals the placement.
|
|
432
|
+
|
|
433
|
+
### Sizing before loading
|
|
434
|
+
|
|
435
|
+
`getAnchoredAdaptiveBannerSize(widthInDp)` returns `{ width, height, heightUnit: "dp" }` on both platforms using the same Google large anchored sizing API as the banner. Call it with a positive available placement width after the window has laid out (Android requires a current Activity). It does not request an ad. The returned height is the collapsed placement; Google presents the expanded creative as an overlay without requiring that extra height in the host. Recalculate when creating a placement at a different width or orientation.
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
> Source snapshot: official ti.admob.unified 1.0.0 documentation at commit `5b4cbf9` (copied 2026-10-11). Relative links rebased for this skill.
|
|
2
|
+
|
|
3
|
+
# Contributing to ti.admob.unified
|
|
4
|
+
|
|
5
|
+
Changes to this native bridge must keep the JavaScript contract consistent with native behavior on both platforms, including cleanup. Tests and documentation must describe that same behavior.
|
|
6
|
+
|
|
7
|
+
## Before opening an issue
|
|
8
|
+
|
|
9
|
+
Include enough information to reproduce the problem:
|
|
10
|
+
|
|
11
|
+
- Titanium SDK version
|
|
12
|
+
- module version
|
|
13
|
+
- platform and OS version
|
|
14
|
+
- device or simulator/emulator model
|
|
15
|
+
- minimal JavaScript example
|
|
16
|
+
- expected and actual behavior
|
|
17
|
+
- complete module and native ad error payloads
|
|
18
|
+
- relevant Xcode or logcat output with secrets removed
|
|
19
|
+
|
|
20
|
+
For layout issues, include the Titanium proxy, host, and parent dimensions plus device density/orientation.
|
|
21
|
+
|
|
22
|
+
## Development baseline
|
|
23
|
+
|
|
24
|
+
- Titanium SDK `13.2.0.GA` or later (build toolchain: `13.4.1.GA`)
|
|
25
|
+
- iOS `15.0+`
|
|
26
|
+
- Android API `24+`, the floor the Titanium `13.2.0.GA` module template sets
|
|
27
|
+
- Node.js with the built-in `node:test` runner
|
|
28
|
+
|
|
29
|
+
Dependency versions are pinned in [DEPENDENCIES.md](dependencies.md).
|
|
30
|
+
|
|
31
|
+
## Workflow
|
|
32
|
+
|
|
33
|
+
Create a focused branch and keep unrelated local changes intact. Before editing a native dependency, establish a recoverable Git baseline and record the upstream source and checksum.
|
|
34
|
+
|
|
35
|
+
This repository holds the core at its root and five dependency-only complements in `mediation/<network>/`. Each has iOS and Android projects at version `1.0.0`. Scripts discover complement folders and derive archive identity/version from manifests. See [Adding a network](#adding-a-network). Applications call only the core. Both script helpers accept a module directory or `--all`.
|
|
36
|
+
|
|
37
|
+
Run the static checks from the repository root:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
npm test
|
|
41
|
+
./scripts/verify-artifacts.sh
|
|
42
|
+
./scripts/verify-artifacts.sh --all
|
|
43
|
+
node --check example/app.js
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Build all discovered artifacts for API or native changes:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
./scripts/prepare-build.sh
|
|
50
|
+
./scripts/prepare-build.sh --all
|
|
51
|
+
./scripts/build-all.sh
|
|
52
|
+
python3 scripts/verify-packages.py
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Never add a mediation network to the core module. Its `-framework` flags belong in `mediation/<network>/ios/module.xcconfig` and its Maven coordinate in `mediation/<network>/android/build.gradle`; a contract test fails if a network name reaches `ios/module.xcconfig`, `android/build.gradle`, the core `pbxproj`, or the core checksum inventory. Applications that use only the core should carry no mediation dependencies.
|
|
56
|
+
|
|
57
|
+
The `example/app.js` file is the module test-harness entry point. It is not a standalone Titanium project.
|
|
58
|
+
|
|
59
|
+
## Adding a network
|
|
60
|
+
|
|
61
|
+
Complements package existing official Google mediation adapters; writing a new native adapter is outside this workflow. No core registration or JavaScript API is needed.
|
|
62
|
+
|
|
63
|
+
1. Copy the source/configuration structure of `mediation/applovin/` to `mediation/<provider>/`, using a lowercase letter followed by lowercase letters/digits for `<provider>`. Work outside `mediation/` until both platform projects are complete: discovery treats every visible subdirectory as a module. Exclude `platform/`, `Pods/`, `build/`, `dist/`, `.gradle/`, generated Android libraries, `metadata.json`, `Podfile.lock`, checksums, Xcode user/workspace files and other caches. Copy no binaries or credentials from the reference network.
|
|
64
|
+
2. Replace `ti.admob.unified.mediation.applovin`, `TiAdmobUnifiedMediationApplovin` and the Java package directory with the new identity everywhere: both manifests and `timodule.xml` files, Java source/file path, Objective-C Module/ModuleAssets headers and implementations, prefix header, `.xcodeproj` directory and `project.pbxproj`, Podfile and framework installer. Generate a GUID with `uuidgen`; use the same value in both manifests and the iOS `moduleGUID`. Update author, description and licenses. Keep matching manifest IDs and versions on both platforms. Preserve all Xcode file references, Sources and Headers registrations while renaming.
|
|
65
|
+
3. Replace `android/build.gradle` with the provider's exact official adapter coordinate and required direct overrides. Use literal `implementation 'group:artifact:version'` declarations (double quotes and parentheses are also accepted), one per line. The verifier deliberately rejects variables, ranges and unsupported dependency syntax. Update repositories in `android/root.build.gradle` if needed. It checks the packaged POM against these declarations plus Titanium/Kotlin build dependencies; the core uses the same check against its own Gradle declarations.
|
|
66
|
+
4. Pin the official iOS adapter in `Podfile`, run `pod install`, and review the new lockfile and transitive versions against [Dependencies](dependencies.md). Adapt `install_frameworks.sh` with the new module path and explicit framework copy map. Stage only the adapter/network frameworks and resources, never core-owned GMA/UMP or frameworks already owned by another complement. Record and review their tree checksums using the installer's `tree_sha256` algorithm. Complete `module.xcconfig` with `$(inherited) -ObjC` and the required framework flags. Keep redistribution notices and privacy resources.
|
|
67
|
+
5. Follow Google's current [Android](https://developers.google.com/admob/android/choose-networks) and [iOS](https://developers.google.com/admob/ios/choose-networks) provider guides. Document required consumer manifest/plist settings, supported formats, consent propagation, publisher accounts and AdMob placement mappings. Do not initialize the provider yourself or invent credentials.
|
|
68
|
+
6. Run `./scripts/prepare-build.sh mediation/<provider>`, then build from each platform directory with `ti build -p android --build-only` or `ti build -p ios --build-only`. Inspect the generated POM and iOS objects/defined symbols. Run the tests and `./scripts/build-all.sh`; all helpers discover the new folder automatically, with two ZIPs per module. Install the resulting ZIPs alongside the core and add `<module version="1.0.0">ti.admob.unified.mediation.<provider></module>` to the consumer's `tiapp.xml` (use the manifest's version).
|
|
69
|
+
7. Test core-only and core-plus-complement consumers on Android and iOS. Query `getMediationStatus()` after `module_initialized`. Test provider delivery with your own configured mediation units, registered test devices, provider test mode and Ad Inspector single-source tests. Record installation/linking and Google initialization separately from actual provider ad delivery. A build or `ready` result does not establish delivery. Keep the public demo core-only with Google's official test IDs.
|
|
70
|
+
|
|
71
|
+
## Platform notes
|
|
72
|
+
|
|
73
|
+
The native implementations share one JavaScript contract. The notes below describe their source files and platform differences.
|
|
74
|
+
|
|
75
|
+
### iOS
|
|
76
|
+
|
|
77
|
+
Sources in `ios/Classes/` comprise five proxy pairs and the module:
|
|
78
|
+
|
|
79
|
+
| Source | Responsibility |
|
|
80
|
+
| --------------------------------------- | --------------------------------------------------------- |
|
|
81
|
+
| `TiAdmobUnifiedModule.*` | Module constants, UMP, ATT, initialization, and factories |
|
|
82
|
+
| `TiAdmobUnifiedBannerAdProxy.*` | Standard banner proxy and native view |
|
|
83
|
+
| `TiAdmobUnifiedAdaptiveBannerAdProxy.*` | Anchored and inline adaptive banner proxy and native view |
|
|
84
|
+
| `TiAdmobUnifiedInterstitialAdProxy.*` | Interstitial proxy |
|
|
85
|
+
| `TiAdmobUnifiedRewardedAdProxy.*` | Rewarded proxy |
|
|
86
|
+
| `TiAdmobUnifiedAppOpenAdProxy.*` | App open proxy and the four hour validity window |
|
|
87
|
+
|
|
88
|
+
Build from the repository root:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
./ios/install_frameworks.sh
|
|
92
|
+
ti build -p ios --build-only --project-dir ios --no-prompt
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`install_frameworks.sh` resolves the pinned CocoaPods dependencies, stages that module's XCFrameworks, verifies their tree hashes, and prepares them for packaging: two frameworks for the core, and the selected network’s frameworks for `mediation/<network>/ios/install_frameworks.sh`. A new `.m` file also has to be registered in `ios/ti.admob.unified.xcodeproj` for inclusion in the static library. An unregistered file can go unnoticed during the module build and cause a link failure in the consuming app.
|
|
96
|
+
|
|
97
|
+
iOS-specific public behavior:
|
|
98
|
+
|
|
99
|
+
- `requestTrackingAuthorization()` requests or retrieves ATT and emits `tracking_authorization_completed`.
|
|
100
|
+
- `createAdaptiveBanner()` implements anchored and inline adaptive banners. Anchored always uses the large variant, because Google deprecated the plain anchored functions.
|
|
101
|
+
- `collapsible` travels as a `GADExtras` network extra, merged with `extras` into one registration. A second `GADExtras` registration for the same network replaces the first.
|
|
102
|
+
- `customAdSize` builds `GADAdSizeFromCGSize`, which is how a fixed full-width strip is requested.
|
|
103
|
+
- `smart_banner` no longer yields a full-width banner. It falls through to `BANNER`, as it does on Android.
|
|
104
|
+
- `setAppVolume()` forwards a validated percentage to Google Mobile Ads, matching Android.
|
|
105
|
+
|
|
106
|
+
### Android
|
|
107
|
+
|
|
108
|
+
Sources live in `android/src/ti/admob/unified/`. The banner types split proxy and view; the fullscreen types do not:
|
|
109
|
+
|
|
110
|
+
| Source | Responsibility |
|
|
111
|
+
| ----------------------------------------------------------- | ---------------------------------------------------- |
|
|
112
|
+
| `TiAdmobUnifiedModule.java` | Module constants, UMP, initialization, and factories |
|
|
113
|
+
| `TiAdmobUnifiedBannerAdProxy.java` / `…AdView.java` | Standard banner proxy and native `AdView` |
|
|
114
|
+
| `TiAdmobUnifiedAdaptiveBannerAdProxy.java` / `…AdView.java` | Adaptive banner proxy and native container |
|
|
115
|
+
| `TiAdmobUnifiedInterstitialAdProxy.java` | Interstitial proxy |
|
|
116
|
+
| `TiAdmobUnifiedRewardedAdProxy.java` | Rewarded proxy |
|
|
117
|
+
| `TiAdmobUnifiedAppOpenAdProxy.java` | App open proxy and the four hour validity window |
|
|
118
|
+
|
|
119
|
+
Maven coordinates are declared in `android/build.gradle`. Build from the module root:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
ti build -p android --build-only --project-dir android --no-prompt
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Android-specific public behavior:
|
|
126
|
+
|
|
127
|
+
- `collapsible` accepts Google's raw placement string, `"top"` or `"bottom"`. Both platforms forward these values to Google; the returned ad may still be non-collapsible.
|
|
128
|
+
- The adaptive `loaded` event carries the same seven size fields on both platforms: `width`, `widthUnit`, `widthPx`, `heightPx`, `density`, `height` and `heightUnit`.
|
|
129
|
+
- `setAppVolume()` accepts a percentage string such as `"50%"` and forwards a normalized value.
|
|
130
|
+
- `isTestDevice()` delegates to `AdRequest.isTestDevice(context)`.
|
|
131
|
+
- Pausing calls the native `AdView.pause()` without changing visibility or layout. Loads requested while paused are deferred until every application/lifecycle pause cause clears. Automatic refresh eligibility still depends on whether Android actually considers the banner visible on screen.
|
|
132
|
+
|
|
133
|
+
### Consumer configuration
|
|
134
|
+
|
|
135
|
+
Neither native module hardcodes application values. The consuming Titanium application owns `GADApplicationIdentifier` and the Android `com.google.android.gms.ads.APPLICATION_ID` metadata, `NSUserTrackingUsageDescription` when it requests ATT, current `SKAdNetworkItems`, production or test ad-unit selection, and the Privacy & messaging configuration in AdMob. See [Getting started](getting-started.md).
|
|
136
|
+
|
|
137
|
+
Do not add a public method or constant on one platform without checking and documenting the other platform's surface, payloads, lifecycle, cleanup, and tests.
|
|
138
|
+
|
|
139
|
+
## Public API changes
|
|
140
|
+
|
|
141
|
+
For a new or changed method, property, event, payload key, or constant:
|
|
142
|
+
|
|
143
|
+
1. Define the portable JavaScript contract first.
|
|
144
|
+
2. Implement it on iOS and Android, or document the platform limitation explicitly.
|
|
145
|
+
3. Preserve existing supported use cases.
|
|
146
|
+
4. Make cleanup and repeated lifecycle calls deterministic.
|
|
147
|
+
5. Add contract tests for both native sources.
|
|
148
|
+
6. Update the API reference and the relevant task guide.
|
|
149
|
+
7. Update `CHANGELOG.md`.
|
|
150
|
+
|
|
151
|
+
Do not export a format constant as evidence that a format is implemented. A new ad format needs a working factory and proxy on both platforms before it is listed as supported.
|
|
152
|
+
|
|
153
|
+
## Privacy changes
|
|
154
|
+
|
|
155
|
+
For privacy changes:
|
|
156
|
+
|
|
157
|
+
- `canRequestAds()` remains the only ad-request gate.
|
|
158
|
+
- Do not parse TCF storage or infer a personalization Boolean.
|
|
159
|
+
- Do not replace UMP with ATT, and do not make ATT a step in the start-up flow. It is optional; an application that resolves it does so before `initialize()`.
|
|
160
|
+
- Every accepted asynchronous operation must emit one modern terminal event.
|
|
161
|
+
- Errors must remain non-blocking to the host application.
|
|
162
|
+
- Debug geography and consent reset belong only in controlled testing.
|
|
163
|
+
|
|
164
|
+
Review [Privacy and initialization](privacy.md) before changing this surface.
|
|
165
|
+
|
|
166
|
+
## Banner changes
|
|
167
|
+
|
|
168
|
+
Standard and adaptive banners must preserve:
|
|
169
|
+
|
|
170
|
+
- `load()`, `pause()`, `resume()`, `isLoaded()`, and terminal `destroy()`;
|
|
171
|
+
- one active request and at most one deferred request;
|
|
172
|
+
- repeatable destruction and one `destroyed` event;
|
|
173
|
+
- removal of native delegates, listeners, controllers, views, and activity lifecycle registrations;
|
|
174
|
+
- common event names and payload keys;
|
|
175
|
+
- real adaptive height in density-independent units;
|
|
176
|
+
- no hardcoded device height and no `setNativeView(null)`.
|
|
177
|
+
|
|
178
|
+
Review [Banner ads](banners.md).
|
|
179
|
+
|
|
180
|
+
## Testing
|
|
181
|
+
|
|
182
|
+
Static tests cannot establish native UI and SDK behavior. Changes affecting that behavior also require validation on physical devices on both platforms:
|
|
183
|
+
|
|
184
|
+
- consent required/not required, accepted/rejected, and previous-session state;
|
|
185
|
+
- ATT authorized, denied, and previously resolved;
|
|
186
|
+
- privacy-options reopening;
|
|
187
|
+
- load, failure, click, impression, show, close, and reward events;
|
|
188
|
+
- background/resume and repeated window lifecycle;
|
|
189
|
+
- orientation, density, safe areas, keyboard, lists, navigation, and snackbars;
|
|
190
|
+
- absence of duplicate ads and retained listeners;
|
|
191
|
+
- official test IDs and Ad Inspector request evidence;
|
|
192
|
+
- `getMediationStatus()` returning the documented types and Google-reported statuses in isolated consumers: core only, each complement, AppLovin + Unity, all five. Test empty/partial maps, an unknown adapter and initialization failures; do not assume this is a complete installed-module inventory. Record simulator and hardware evidence separately; adapter presence and Google test ads do not prove third-party delivery.
|
|
193
|
+
|
|
194
|
+
Never click production ads while developing.
|
|
195
|
+
|
|
196
|
+
## Documentation
|
|
197
|
+
|
|
198
|
+
Keep documentation in English and follow these requirements:
|
|
199
|
+
|
|
200
|
+
- describe only behavior implemented by the referenced version;
|
|
201
|
+
- include argument types, return values, events, and payloads;
|
|
202
|
+
- separate portable behavior from platform-specific behavior;
|
|
203
|
+
- mark deprecated aliases and non-working reserved APIs;
|
|
204
|
+
- use official test constants in executable examples;
|
|
205
|
+
- link to primary Google or Apple documentation for upstream requirements.
|
|
206
|
+
|
|
207
|
+
`tests/documentation.test.js` checks method/constant coverage, internal links, and unsafe example regressions.
|
|
208
|
+
|
|
209
|
+
## Pull requests
|
|
210
|
+
|
|
211
|
+
A pull request should state:
|
|
212
|
+
|
|
213
|
+
- the problem and intended contract;
|
|
214
|
+
- iOS and Android implementation impact;
|
|
215
|
+
- compatibility or migration impact;
|
|
216
|
+
- tests and builds executed;
|
|
217
|
+
- devices and scenarios validated;
|
|
218
|
+
- documentation updated.
|
|
219
|
+
|
|
220
|
+
Do not publish artifacts or create a release tag from a contribution branch. Release signing, tags, and distribution remain maintainer actions.
|
|
221
|
+
|
|
222
|
+
Contributions are licensed under the [MIT License](module-license.txt).
|
|
223
|
+
|
|
224
|
+
### Native wrapper behavior harness
|
|
225
|
+
|
|
226
|
+
Run `python3 tests/native/android.py` with a JDK, and `python3 tests/native/run.py` on macOS. The Java harness checks percentage validation, the TCF preference store and consent reset ordering. It compiles the current Objective-C method bodies against controlled Foundation-based SDK stand-ins and checks volume validation, load-error mapping, consent retry/reset ordering and delayed reward callbacks. It does not replace a Titanium consumer build, native bridge checks or Google ad presentation tests. `npm test` separately exercises the JavaScript library and example, alongside static contract checks.
|
|
227
|
+
|
|
228
|
+
After individual platform builds, run `python3 scripts/prepare-package-docs.py` before `python3 scripts/verify-packages.py`. Titanium renders guides as HTML without rewriting their Markdown links. The preparation step fixes links between generated guides and includes the linked Markdown sources for offline use. `scripts/build-all.sh` runs both steps automatically.
|