@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,618 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Copy-ready TiAdMobUnified integration.
|
|
5
|
+
*
|
|
6
|
+
* Alloy: app/lib/ads.js
|
|
7
|
+
* Classic: Resources/lib/ads.js
|
|
8
|
+
*
|
|
9
|
+
* The Google Mobile Ads SDK owns banner refresh. This library never creates a
|
|
10
|
+
* refresh timer and never changes banner visibility while pausing.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const Admob = require('ti.admob.unified')
|
|
14
|
+
|
|
15
|
+
const resources = new Set()
|
|
16
|
+
|
|
17
|
+
let startup = null
|
|
18
|
+
let startupOptions = {}
|
|
19
|
+
let gateResolved = false
|
|
20
|
+
let canRequest = false
|
|
21
|
+
let sdkReady = false
|
|
22
|
+
let privacyOperation = null
|
|
23
|
+
let inspectorOperation = null
|
|
24
|
+
|
|
25
|
+
function call(handler, event) {
|
|
26
|
+
if (typeof handler !== 'function') {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
handler(event || {})
|
|
32
|
+
} catch (error) {
|
|
33
|
+
Ti.API.error(`[ads] Event handler failed: ${error.message || error}`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function waitForEvent(name, invoke) {
|
|
38
|
+
return new Promise(resolve => {
|
|
39
|
+
let settled = false
|
|
40
|
+
|
|
41
|
+
function finish(event) {
|
|
42
|
+
if (settled) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
settled = true
|
|
47
|
+
Admob.removeEventListener(name, finish)
|
|
48
|
+
resolve(event || {})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Admob.addEventListener(name, finish)
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
invoke()
|
|
55
|
+
} catch (error) {
|
|
56
|
+
finish({ success: false, error: error.message || String(error) })
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function synchronizeResources(method) {
|
|
62
|
+
Array.from(resources).forEach(resource => {
|
|
63
|
+
if (typeof resource[method] === 'function') {
|
|
64
|
+
resource[method]()
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function initializeIfAllowed() {
|
|
70
|
+
canRequest = Admob.canRequestAds()
|
|
71
|
+
|
|
72
|
+
if (!canRequest) {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (sdkReady) {
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const initialized = await waitForEvent('module_initialized', () => {
|
|
81
|
+
Admob.initialize(startupOptions.initialization || {})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
sdkReady = initialized.success === true || initialized.success === 1
|
|
85
|
+
if (!sdkReady) {
|
|
86
|
+
Ti.API.warn(`[ads] Google Mobile Ads initialization failed: ${initialized.error || 'unknown error'}`)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return sdkReady
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Runs UMP -> optional ATT -> eligibility -> SDK initialization once.
|
|
94
|
+
*
|
|
95
|
+
* options.consent: dictionary passed to requestConsent()
|
|
96
|
+
* options.requestTrackingAuthorization: true only when the app has a real ATT use case
|
|
97
|
+
* options.initialization: dictionary passed to initialize()
|
|
98
|
+
*/
|
|
99
|
+
function start(options) {
|
|
100
|
+
if (startup) {
|
|
101
|
+
return startup
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
startupOptions = options || {}
|
|
105
|
+
startup = (async () => {
|
|
106
|
+
const consent = await waitForEvent('consent_complete', () => {
|
|
107
|
+
Admob.requestConsent(startupOptions.consent || {})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
if (consent.success === false || consent.success === 0) {
|
|
111
|
+
Ti.API.warn(`[ads] UMP completed with an error: ${consent.error || 'unknown error'}`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let tracking = null
|
|
115
|
+
if (startupOptions.requestTrackingAuthorization === true) {
|
|
116
|
+
tracking = await waitForEvent('tracking_authorization_completed', () => {
|
|
117
|
+
Admob.requestTrackingAuthorization()
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await initializeIfAllowed()
|
|
122
|
+
gateResolved = true
|
|
123
|
+
synchronizeResources('sync')
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
consent,
|
|
127
|
+
tracking,
|
|
128
|
+
canRequestAds: canRequest,
|
|
129
|
+
initialized: sdkReady
|
|
130
|
+
}
|
|
131
|
+
})()
|
|
132
|
+
|
|
133
|
+
return startup
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function addListener(proxy, listeners, name, handler) {
|
|
137
|
+
if (!name || typeof handler !== 'function') {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
proxy.addEventListener(name, handler)
|
|
142
|
+
listeners.push([name, handler])
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function removeListeners(proxy, listeners) {
|
|
146
|
+
listeners.forEach(entry => proxy.removeEventListener(entry[0], entry[1]))
|
|
147
|
+
listeners.length = 0
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function bannerProperties(options, adaptive) {
|
|
151
|
+
const properties = {
|
|
152
|
+
adUnitId: options.adUnitId,
|
|
153
|
+
width: options.width || Ti.UI.FILL
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const key of ['keywords', 'contentUrl', 'requestAgent', 'extras']) {
|
|
157
|
+
if (options[key] !== undefined) {
|
|
158
|
+
properties[key] = options[key]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (adaptive) {
|
|
163
|
+
if (Ti.Platform && Ti.Platform.osname !== 'android') properties.height = Ti.UI.FILL
|
|
164
|
+
properties.adaptiveType = options.adaptiveType || Admob.ADAPTIVE_ANCHORED
|
|
165
|
+
for (const key of ['maxHeight', 'collapsible']) {
|
|
166
|
+
if (options[key] !== undefined) {
|
|
167
|
+
properties[key] = options[key]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
properties.adSize = options.adSize || Admob.BANNER
|
|
172
|
+
if (options.customAdSize !== undefined) {
|
|
173
|
+
properties.customAdSize = options.customAdSize
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return properties
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function createBannerPlacement(options, adaptive) {
|
|
181
|
+
if (!options || !options.host || !options.adUnitId) {
|
|
182
|
+
throw new Error('A banner requires host and adUnitId')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const host = options.host
|
|
186
|
+
const content = options.content
|
|
187
|
+
const reservedHeight = Number(options.reservedHeight)
|
|
188
|
+
|
|
189
|
+
if (!Number.isFinite(reservedHeight) || reservedHeight <= 0) {
|
|
190
|
+
throw new Error('A banner requires a positive reservedHeight in dp')
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let proxy = null
|
|
194
|
+
let listeners = []
|
|
195
|
+
let destroyed = false
|
|
196
|
+
let locallyPaused = options.paused === true
|
|
197
|
+
|
|
198
|
+
function reserve(height) {
|
|
199
|
+
const dimension = `${Math.max(0, Number(height) || 0)}dp`
|
|
200
|
+
host.height = dimension
|
|
201
|
+
if (content) {
|
|
202
|
+
content.bottom = dimension
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function detach(destroyNative) {
|
|
207
|
+
if (!proxy) {
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const target = proxy
|
|
212
|
+
proxy = null
|
|
213
|
+
removeListeners(target, listeners)
|
|
214
|
+
|
|
215
|
+
if (destroyNative) {
|
|
216
|
+
target.destroy()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
host.remove(target)
|
|
221
|
+
} catch (error) {
|
|
222
|
+
Ti.API.debug(`[ads] Banner was already detached: ${error.message || error}`)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function onLoaded(event) {
|
|
227
|
+
const actualHeight = Number(event.height)
|
|
228
|
+
reserve(actualHeight > 0 ? actualHeight : reservedHeight)
|
|
229
|
+
call(options.onLoaded, event)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function onFailedToLoad(event) {
|
|
233
|
+
detach(true)
|
|
234
|
+
if (options.collapseOnFailure !== false) {
|
|
235
|
+
reserve(0)
|
|
236
|
+
}
|
|
237
|
+
call(options.onFailedToLoad, event)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function onDestroyed(event) {
|
|
241
|
+
detach(false)
|
|
242
|
+
reserve(0)
|
|
243
|
+
call(options.onDestroyed, event)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function attachForwarder(name, optionName) {
|
|
247
|
+
addListener(proxy, listeners, name, event => call(options[optionName], event))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const placement = {
|
|
251
|
+
sync() {
|
|
252
|
+
if (destroyed || locallyPaused || proxy) {
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!canRequest || !sdkReady) {
|
|
257
|
+
if (gateResolved) {
|
|
258
|
+
reserve(0)
|
|
259
|
+
}
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
reserve(reservedHeight)
|
|
264
|
+
proxy = adaptive
|
|
265
|
+
? Admob.createAdaptiveBanner(bannerProperties(options, true))
|
|
266
|
+
: Admob.createBanner(bannerProperties(options, false))
|
|
267
|
+
|
|
268
|
+
addListener(proxy, listeners, Admob.AD_LOADED, onLoaded)
|
|
269
|
+
addListener(proxy, listeners, Admob.AD_FAILED_TO_LOAD, onFailedToLoad)
|
|
270
|
+
addListener(proxy, listeners, Admob.AD_DESTROYED, onDestroyed)
|
|
271
|
+
attachForwarder(Admob.AD_CLICKED, 'onClicked')
|
|
272
|
+
attachForwarder(Admob.AD_IMPRESSION, 'onImpression')
|
|
273
|
+
attachForwarder(Admob.AD_OPENED, 'onOpened')
|
|
274
|
+
attachForwarder(Admob.AD_CLOSED, 'onClosed')
|
|
275
|
+
|
|
276
|
+
if (adaptive && Ti.Platform && Ti.Platform.osname !== 'android') {
|
|
277
|
+
const target = proxy
|
|
278
|
+
const anchored = !options.adaptiveType || options.adaptiveType === Admob.ADAPTIVE_ANCHORED
|
|
279
|
+
let measuredWidth = 0
|
|
280
|
+
function prepareSize(width) {
|
|
281
|
+
if (!anchored || width <= 0 || measuredWidth === width) return false
|
|
282
|
+
measuredWidth = width
|
|
283
|
+
const height = Admob.getAnchoredAdaptiveBannerSize(width).height
|
|
284
|
+
const changed = host.height !== `${height}dp`
|
|
285
|
+
reserve(height)
|
|
286
|
+
return changed
|
|
287
|
+
}
|
|
288
|
+
prepareSize(host.rect ? host.rect.width : 0)
|
|
289
|
+
function loadAfterLayout() {
|
|
290
|
+
if (destroyed || proxy !== target || target.rect.width <= 0 || target.rect.height <= 0) return
|
|
291
|
+
if (prepareSize(target.rect.width)) return
|
|
292
|
+
target.removeEventListener('postlayout', loadAfterLayout)
|
|
293
|
+
target.load()
|
|
294
|
+
}
|
|
295
|
+
addListener(target, listeners, 'postlayout', loadAfterLayout)
|
|
296
|
+
host.add(target)
|
|
297
|
+
} else {
|
|
298
|
+
host.add(proxy)
|
|
299
|
+
proxy.load()
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
pause() {
|
|
304
|
+
if (destroyed || locallyPaused) {
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
locallyPaused = true
|
|
309
|
+
if (proxy) {
|
|
310
|
+
proxy.pause()
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
resume() {
|
|
315
|
+
if (destroyed || !locallyPaused) {
|
|
316
|
+
return
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
locallyPaused = false
|
|
320
|
+
if (proxy) {
|
|
321
|
+
proxy.resume()
|
|
322
|
+
}
|
|
323
|
+
placement.sync()
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
rebuild() {
|
|
327
|
+
if (destroyed) {
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
detach(true)
|
|
332
|
+
reserve(reservedHeight)
|
|
333
|
+
placement.sync()
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
retry() {
|
|
337
|
+
placement.rebuild()
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
authorizationChanged() {
|
|
341
|
+
if (destroyed) {
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
detach(true)
|
|
346
|
+
reserve(canRequest && sdkReady ? reservedHeight : 0)
|
|
347
|
+
placement.sync()
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
isLoaded() {
|
|
351
|
+
return Boolean(proxy && proxy.isLoaded())
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
destroy() {
|
|
355
|
+
if (destroyed) {
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
destroyed = true
|
|
360
|
+
resources.delete(placement)
|
|
361
|
+
detach(true)
|
|
362
|
+
reserve(0)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
resources.add(placement)
|
|
367
|
+
reserve(reservedHeight)
|
|
368
|
+
placement.sync()
|
|
369
|
+
return placement
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function createBanner(options) {
|
|
373
|
+
return createBannerPlacement(options, false)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function createAdaptiveBanner(options) {
|
|
377
|
+
return createBannerPlacement(options, true)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const fullscreenEvents = [
|
|
381
|
+
['AD_LOADED', 'onLoaded'],
|
|
382
|
+
['AD_FAILED_TO_LOAD', 'onFailedToLoad'],
|
|
383
|
+
['AD_OPENED', 'onOpened'],
|
|
384
|
+
['AD_SHOWED_FULLSCREEN_CONTENT', 'onShowed'],
|
|
385
|
+
['AD_CLOSED', 'onClosed'],
|
|
386
|
+
['AD_CLICKED', 'onClicked'],
|
|
387
|
+
['AD_IMPRESSION', 'onImpression'],
|
|
388
|
+
['AD_FAILED_TO_SHOW', 'onFailedToShow'],
|
|
389
|
+
['AD_REWARDED', 'onRewarded'],
|
|
390
|
+
['AD_DESTROYED', 'onDestroyed']
|
|
391
|
+
]
|
|
392
|
+
|
|
393
|
+
function fullscreenProperties(options) {
|
|
394
|
+
const properties = {
|
|
395
|
+
adUnitId: options.adUnitId,
|
|
396
|
+
autoReload: options.autoReload === true
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
for (const key of ['keywords', 'requestAgent', 'extras']) {
|
|
400
|
+
if (options[key] !== undefined) {
|
|
401
|
+
properties[key] = options[key]
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return properties
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Shared by the managers and the raw-API playground. Rotation alone does no work.
|
|
409
|
+
function createFullscreenSizeGuard(getSize) {
|
|
410
|
+
let requestedSize = null
|
|
411
|
+
function sizeKey() {
|
|
412
|
+
const size = getSize()
|
|
413
|
+
return size && size.width > 0 && size.height > 0
|
|
414
|
+
? `${Math.round(size.width)}x${Math.round(size.height)}` : null
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
recordLoad() { requestedSize = sizeKey() },
|
|
418
|
+
show(present, reload) {
|
|
419
|
+
const currentSize = sizeKey()
|
|
420
|
+
if (currentSize && currentSize !== requestedSize) {
|
|
421
|
+
reload()
|
|
422
|
+
return false
|
|
423
|
+
}
|
|
424
|
+
present()
|
|
425
|
+
return true
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function createFullscreen(options, factoryName) {
|
|
431
|
+
if (!options || !options.adUnitId) {
|
|
432
|
+
throw new Error(`${factoryName} requires adUnitId`)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
let proxy = null
|
|
436
|
+
let listeners = []
|
|
437
|
+
let destroyed = false
|
|
438
|
+
let loadRequested = options.autoLoad === true
|
|
439
|
+
let loading = false
|
|
440
|
+
const sizeGuard = createFullscreenSizeGuard(() => (options.window || Ti.UI.currentWindow)?.size)
|
|
441
|
+
|
|
442
|
+
function detach(destroyNative) {
|
|
443
|
+
if (!proxy) {
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const target = proxy
|
|
448
|
+
proxy = null
|
|
449
|
+
loading = false
|
|
450
|
+
removeListeners(target, listeners)
|
|
451
|
+
if (destroyNative) {
|
|
452
|
+
target.destroy()
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const manager = {
|
|
457
|
+
sync() {
|
|
458
|
+
if (destroyed || proxy || !loadRequested || !canRequest || !sdkReady) {
|
|
459
|
+
return
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
proxy = Admob[factoryName](fullscreenProperties(options))
|
|
463
|
+
|
|
464
|
+
fullscreenEvents.forEach(entry => {
|
|
465
|
+
const eventName = Admob[entry[0]]
|
|
466
|
+
if (!eventName) {
|
|
467
|
+
return
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
addListener(proxy, listeners, eventName, event => {
|
|
471
|
+
if (entry[0] === 'AD_LOADED' || entry[0] === 'AD_FAILED_TO_LOAD') loading = false
|
|
472
|
+
if (entry[0] === 'AD_CLOSED' && options.autoReload === true) {
|
|
473
|
+
loading = true
|
|
474
|
+
sizeGuard.recordLoad()
|
|
475
|
+
}
|
|
476
|
+
if (entry[0] === 'AD_CLOSED' && options.autoReload !== true) {
|
|
477
|
+
loadRequested = false
|
|
478
|
+
}
|
|
479
|
+
if (entry[0] === 'AD_FAILED_TO_SHOW') {
|
|
480
|
+
loadRequested = false
|
|
481
|
+
}
|
|
482
|
+
if (entry[0] === 'AD_DESTROYED') {
|
|
483
|
+
detach(false)
|
|
484
|
+
call(options[entry[1]], event)
|
|
485
|
+
return
|
|
486
|
+
}
|
|
487
|
+
call(options[entry[1]], event)
|
|
488
|
+
})
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
loading = true
|
|
492
|
+
sizeGuard.recordLoad()
|
|
493
|
+
proxy.load()
|
|
494
|
+
},
|
|
495
|
+
|
|
496
|
+
load() {
|
|
497
|
+
if (destroyed || loading) {
|
|
498
|
+
return false
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
loadRequested = true
|
|
502
|
+
if (proxy) {
|
|
503
|
+
loading = true
|
|
504
|
+
sizeGuard.recordLoad()
|
|
505
|
+
proxy.load()
|
|
506
|
+
} else {
|
|
507
|
+
manager.sync()
|
|
508
|
+
}
|
|
509
|
+
return true
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
show() {
|
|
513
|
+
if (!proxy || loading || !proxy.isLoaded()) {
|
|
514
|
+
return false
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return sizeGuard.show(() => proxy.show(), () => manager.retry())
|
|
518
|
+
},
|
|
519
|
+
|
|
520
|
+
isLoaded() {
|
|
521
|
+
return Boolean(proxy && proxy.isLoaded())
|
|
522
|
+
},
|
|
523
|
+
|
|
524
|
+
retry() {
|
|
525
|
+
if (destroyed) {
|
|
526
|
+
return false
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
detach(true)
|
|
530
|
+
loadRequested = true
|
|
531
|
+
manager.sync()
|
|
532
|
+
return true
|
|
533
|
+
},
|
|
534
|
+
|
|
535
|
+
authorizationChanged() {
|
|
536
|
+
if (destroyed) {
|
|
537
|
+
return
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
detach(true)
|
|
541
|
+
manager.sync()
|
|
542
|
+
},
|
|
543
|
+
|
|
544
|
+
destroy() {
|
|
545
|
+
if (destroyed) {
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
destroyed = true
|
|
550
|
+
resources.delete(manager)
|
|
551
|
+
detach(true)
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
resources.add(manager)
|
|
556
|
+
manager.sync()
|
|
557
|
+
return manager
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function createInterstitial(options) {
|
|
561
|
+
return createFullscreen(options, 'createInterstitial')
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function createRewarded(options) {
|
|
565
|
+
return createFullscreen(options, 'createRewarded')
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function createAppOpen(options) {
|
|
569
|
+
return createFullscreen(options, 'createAppOpen')
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function showPrivacyOptions() {
|
|
573
|
+
if (privacyOperation) return privacyOperation
|
|
574
|
+
privacyOperation = (async () => {
|
|
575
|
+
const result = await waitForEvent('privacy_options_dismissed', () => {
|
|
576
|
+
Admob.presentPrivacyOptions({})
|
|
577
|
+
})
|
|
578
|
+
|
|
579
|
+
await initializeIfAllowed()
|
|
580
|
+
synchronizeResources('authorizationChanged')
|
|
581
|
+
return result
|
|
582
|
+
})().finally(() => { privacyOperation = null })
|
|
583
|
+
return privacyOperation
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function isPrivacyOptionsRequired() {
|
|
587
|
+
return Admob.isPrivacyOptionsRequired()
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function openAdInspector() {
|
|
591
|
+
if (inspectorOperation) return inspectorOperation
|
|
592
|
+
inspectorOperation = waitForEvent('ad_inspector_closed', () => Admob.openAdInspector())
|
|
593
|
+
.finally(() => { inspectorOperation = null })
|
|
594
|
+
return inspectorOperation
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function isReady() {
|
|
598
|
+
return canRequest && sdkReady
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function destroyAll() {
|
|
602
|
+
Array.from(resources).forEach(resource => resource.destroy())
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
module.exports = {
|
|
606
|
+
start,
|
|
607
|
+
isReady,
|
|
608
|
+
isPrivacyOptionsRequired,
|
|
609
|
+
showPrivacyOptions,
|
|
610
|
+
openAdInspector,
|
|
611
|
+
createBanner,
|
|
612
|
+
createAdaptiveBanner,
|
|
613
|
+
createFullscreenSizeGuard,
|
|
614
|
+
createInterstitial,
|
|
615
|
+
createRewarded,
|
|
616
|
+
createAppOpen,
|
|
617
|
+
destroyAll
|
|
618
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Usage of example/lib/ads.js after copying it to the consuming app's lib/.
|
|
5
|
+
* This file is intentionally separate from app.js, which demonstrates the raw
|
|
6
|
+
* module API. Pick one integration style; do not initialize both.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const Admob = require('ti.admob.unified')
|
|
10
|
+
const ads = require('lib/ads')
|
|
11
|
+
|
|
12
|
+
const win = Ti.UI.createWindow({ layout: 'composite', backgroundColor: '#ffffff' })
|
|
13
|
+
const content = Ti.UI.createScrollView({ top: 0, left: 0, right: 0, bottom: 0 })
|
|
14
|
+
const bannerHost = Ti.UI.createView({ left: 0, right: 0, bottom: 0, height: 50 })
|
|
15
|
+
|
|
16
|
+
win.add(content)
|
|
17
|
+
win.add(bannerHost)
|
|
18
|
+
|
|
19
|
+
const banner = ads.createBanner({
|
|
20
|
+
host: bannerHost,
|
|
21
|
+
content,
|
|
22
|
+
reservedHeight: 50,
|
|
23
|
+
adUnitId: Admob.TEST_BANNER,
|
|
24
|
+
adSize: Admob.BANNER
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Use this instead of createBanner() when the placement should adapt to width.
|
|
28
|
+
function createAdaptiveBanner() {
|
|
29
|
+
banner.destroy()
|
|
30
|
+
return ads.createAdaptiveBanner({
|
|
31
|
+
host: bannerHost,
|
|
32
|
+
content,
|
|
33
|
+
reservedHeight: 50,
|
|
34
|
+
adUnitId: Admob.TEST_ADAPTIVE_BANNER,
|
|
35
|
+
adaptiveType: Admob.ADAPTIVE_ANCHORED
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const interstitial = ads.createInterstitial({
|
|
40
|
+
window: win,
|
|
41
|
+
adUnitId: Admob.TEST_INTERSTITIAL,
|
|
42
|
+
onLoaded: () => Ti.API.info('[ads] Interstitial ready')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const rewarded = ads.createRewarded({
|
|
46
|
+
window: win,
|
|
47
|
+
adUnitId: Admob.TEST_REWARDED,
|
|
48
|
+
onRewarded: event => grantReward(event.rewardAmount, event.rewardType)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const appOpen = ads.createAppOpen({
|
|
52
|
+
window: win,
|
|
53
|
+
adUnitId: Admob.TEST_APP_OPEN,
|
|
54
|
+
onLoaded: () => Ti.API.info('[ads] App-open ready for the next foreground')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
function grantReward(amount, type) {
|
|
58
|
+
Ti.API.info(`[ads] Grant ${amount} ${type}`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function onNaturalBreak() {
|
|
62
|
+
interstitial.show()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function onRewardAccepted() {
|
|
66
|
+
rewarded.show()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function onApplicationResumed() {
|
|
70
|
+
appOpen.show()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function openChildWindow() {
|
|
74
|
+
const child = Ti.UI.createWindow({ backgroundColor: '#ffffff' })
|
|
75
|
+
banner.pause()
|
|
76
|
+
child.addEventListener('close', () => banner.resume())
|
|
77
|
+
child.open()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
win.addEventListener('close', event => {
|
|
81
|
+
if (!event._closeFromActivityForcedToDestroy) ads.destroyAll()
|
|
82
|
+
})
|
|
83
|
+
win.open()
|
|
84
|
+
|
|
85
|
+
ads.start({
|
|
86
|
+
consent: { tagForUnderAgeOfConsent: false },
|
|
87
|
+
initialization: {}
|
|
88
|
+
}).then(result => {
|
|
89
|
+
if (!result.initialized) {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interstitial.load()
|
|
94
|
+
rewarded.load()
|
|
95
|
+
appOpen.load()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// References kept here to make the intended call sites explicit in this
|
|
99
|
+
// compact example. Real apps connect them to their own navigation/lifecycle.
|
|
100
|
+
void createAdaptiveBanner
|
|
101
|
+
void onNaturalBreak
|
|
102
|
+
void onRewardAccepted
|
|
103
|
+
void onApplicationResumed
|
|
104
|
+
void openChildWindow
|