@thealteroffice/react-native-adgeist 0.0.16 → 0.0.18
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/android/build.gradle +1 -1
- package/android/generated/java/com/adgeist/NativeAdgeistSpec.java +83 -0
- package/android/generated/jni/CMakeLists.txt +36 -0
- package/android/generated/jni/RNAdgeistSpec-generated.cpp +98 -0
- package/android/generated/jni/RNAdgeistSpec.h +31 -0
- package/android/generated/jni/react/renderer/components/RNAdgeistSpec/RNAdgeistSpecJSI-generated.cpp +149 -0
- package/android/generated/jni/react/renderer/components/RNAdgeistSpec/RNAdgeistSpecJSI.h +170 -0
- package/android/src/main/java/com/adgeist/implementation/AdgeistModuleImpl.kt +101 -9
- package/android/src/newarch/java/com/AdgeistModule.kt +93 -2
- package/android/src/oldarch/java/com/AdgeistModule.kt +101 -4
- package/ios/generated/RNAdgeistSpec/RNAdgeistSpec-generated.mm +42 -7
- package/ios/generated/RNAdgeistSpec/RNAdgeistSpec.h +57 -10
- package/ios/generated/RNAdgeistSpecJSI-generated.cpp +81 -14
- package/ios/generated/RNAdgeistSpecJSI.h +54 -9
- package/lib/module/NativeAdgeist.js.map +1 -1
- package/lib/module/components/BannerAd.js +201 -63
- package/lib/module/components/BannerAd.js.map +1 -1
- package/lib/module/utilities.js +16 -0
- package/lib/module/utilities.js.map +1 -0
- package/lib/typescript/src/NativeAdgeist.d.ts +6 -1
- package/lib/typescript/src/NativeAdgeist.d.ts.map +1 -1
- package/lib/typescript/src/components/BannerAd.d.ts.map +1 -1
- package/lib/typescript/src/utilities.d.ts +5 -0
- package/lib/typescript/src/utilities.d.ts.map +1 -0
- package/package.json +8 -2
- package/src/NativeAdgeist.ts +61 -9
- package/src/components/BannerAd.tsx +283 -77
- package/src/utilities.ts +13 -0
- package/plugin/build/android/withRNAdgeistMainApplication.d.ts +0 -3
- package/plugin/build/android/withRNAdgeistMainApplication.js +0 -65
- package/plugin/build/index.d.ts +0 -3
- package/plugin/build/index.js +0 -25
- package/plugin/build/ios/withRNAdgeistAppDelegate.d.ts +0 -4
- package/plugin/build/ios/withRNAdgeistAppDelegate.js +0 -66
package/src/NativeAdgeist.ts
CHANGED
|
@@ -23,24 +23,76 @@ export interface Spec extends TurboModule {
|
|
|
23
23
|
isTestEnvironment: boolean
|
|
24
24
|
): Promise<Object>;
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
setUserDetails(user: Object): void;
|
|
27
|
+
|
|
28
|
+
logEvent(event: Object): void;
|
|
29
|
+
|
|
30
|
+
getConsentStatus(): Promise<boolean>;
|
|
31
|
+
|
|
32
|
+
updateConsentStatus(consent: boolean): void;
|
|
33
|
+
|
|
34
|
+
trackImpression(
|
|
27
35
|
campaignId: string,
|
|
28
36
|
adSpaceId: string,
|
|
29
37
|
publisherId: string,
|
|
30
|
-
eventType: string,
|
|
31
|
-
origin: string,
|
|
32
38
|
apiKey: string,
|
|
33
39
|
bidId: string,
|
|
34
|
-
isTestEnvironment: boolean
|
|
35
|
-
|
|
40
|
+
isTestEnvironment: boolean,
|
|
41
|
+
renderTime: number
|
|
42
|
+
): Promise<string>;
|
|
36
43
|
|
|
37
|
-
|
|
44
|
+
trackView(
|
|
45
|
+
campaignId: string,
|
|
46
|
+
adSpaceId: string,
|
|
47
|
+
publisherId: string,
|
|
48
|
+
apiKey: string,
|
|
49
|
+
bidId: string,
|
|
50
|
+
isTestEnvironment: boolean,
|
|
51
|
+
viewTime: number,
|
|
52
|
+
visibilityRatio: number,
|
|
53
|
+
scrollDepth: number,
|
|
54
|
+
timeToVisible: number
|
|
55
|
+
): Promise<string>;
|
|
38
56
|
|
|
39
|
-
|
|
57
|
+
trackTotalView(
|
|
58
|
+
campaignId: string,
|
|
59
|
+
adSpaceId: string,
|
|
60
|
+
publisherId: string,
|
|
61
|
+
apiKey: string,
|
|
62
|
+
bidId: string,
|
|
63
|
+
isTestEnvironment: boolean,
|
|
64
|
+
totalViewTime: number,
|
|
65
|
+
visibilityRatio: number
|
|
66
|
+
): Promise<string>;
|
|
40
67
|
|
|
41
|
-
|
|
68
|
+
trackClick(
|
|
69
|
+
campaignId: string,
|
|
70
|
+
adSpaceId: string,
|
|
71
|
+
publisherId: string,
|
|
72
|
+
apiKey: string,
|
|
73
|
+
bidId: string,
|
|
74
|
+
isTestEnvironment: boolean
|
|
75
|
+
): Promise<string>;
|
|
42
76
|
|
|
43
|
-
|
|
77
|
+
trackVideoPlayback(
|
|
78
|
+
campaignId: string,
|
|
79
|
+
adSpaceId: string,
|
|
80
|
+
publisherId: string,
|
|
81
|
+
apiKey: string,
|
|
82
|
+
bidId: string,
|
|
83
|
+
isTestEnvironment: boolean,
|
|
84
|
+
totalPlaybackTime: number
|
|
85
|
+
): Promise<string>;
|
|
86
|
+
|
|
87
|
+
trackVideoQuartile(
|
|
88
|
+
campaignId: string,
|
|
89
|
+
adSpaceId: string,
|
|
90
|
+
publisherId: string,
|
|
91
|
+
apiKey: string,
|
|
92
|
+
bidId: string,
|
|
93
|
+
isTestEnvironment: boolean,
|
|
94
|
+
quartile: string
|
|
95
|
+
): Promise<string>;
|
|
44
96
|
}
|
|
45
97
|
|
|
46
98
|
export default TurboModuleRegistry.getEnforcing<Spec>('Adgeist');
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @description A React Native component for displaying banner and video ads with robust error handling and analytics.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React, { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
import React, { useCallback, useEffect, useState, useRef } from 'react';
|
|
7
7
|
import {
|
|
8
8
|
Image,
|
|
9
9
|
Linking,
|
|
@@ -12,10 +12,12 @@ import {
|
|
|
12
12
|
View,
|
|
13
13
|
TouchableWithoutFeedback,
|
|
14
14
|
ActivityIndicator,
|
|
15
|
+
Dimensions,
|
|
15
16
|
} from 'react-native';
|
|
16
17
|
import Video from 'react-native-video';
|
|
17
18
|
import Adgeist from '../NativeAdgeist';
|
|
18
19
|
import { useAdgeistContext } from './AdgeistProvider';
|
|
20
|
+
import { normalizeUrl } from '../utilities';
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Interface for ad data structure
|
|
@@ -84,22 +86,37 @@ export const BannerAd: React.FC<AdBannerProps> = ({
|
|
|
84
86
|
width = 0,
|
|
85
87
|
height = 0,
|
|
86
88
|
isResponsive = false,
|
|
87
|
-
// responsiveType = 'SQUARE',
|
|
88
89
|
onAdLoadError,
|
|
89
90
|
onAdLoadSuccess,
|
|
90
91
|
}) => {
|
|
91
92
|
const [adData, setAdData] = useState<AdData | null>(null);
|
|
92
|
-
const [isMuted, setIsMuted] = useState<boolean>(
|
|
93
|
+
const [isMuted, setIsMuted] = useState<boolean>(true);
|
|
93
94
|
const [error, setError] = useState<Error | null>(null);
|
|
94
95
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
96
|
+
const [hasImpression, setHasImpression] = useState<boolean>(false);
|
|
97
|
+
const [hasView, setHasView] = useState<boolean>(false);
|
|
98
|
+
const [isPaused, setIsPaused] = useState<boolean>(true);
|
|
99
|
+
const [isManuallyControlled, setIsManuallyControlled] =
|
|
100
|
+
useState<boolean>(false);
|
|
101
|
+
const renderStartTime = useRef(Date.now());
|
|
102
|
+
const adRef = useRef<View>(null);
|
|
103
|
+
const visibilityStartTime = useRef<number | null>(null);
|
|
104
|
+
const timeToVisible = useRef<number | null>(null);
|
|
105
|
+
const viewTime = useRef<number>(0);
|
|
106
|
+
const lastCheckTime = useRef<number>(Date.now());
|
|
107
|
+
const currentVisibilityRatio = useRef<number>(0);
|
|
108
|
+
const videoRef = useRef<any>(null);
|
|
95
109
|
|
|
96
110
|
const { isInitialized, publisherId, apiKey, domain, isTestEnvironment } =
|
|
97
111
|
useAdgeistContext();
|
|
98
112
|
|
|
99
113
|
const creativeData = adData?.seatBid?.[0]?.bid?.[0]?.ext as BidExtension;
|
|
114
|
+
const bidId = adData?.id;
|
|
115
|
+
const campaignId = adData?.seatBid?.[0]?.bid?.[0]?.id;
|
|
116
|
+
const adSpaceId = dataAdSlot;
|
|
100
117
|
|
|
101
118
|
/**
|
|
102
|
-
* Fetches ad creative
|
|
119
|
+
* Fetches ad creative data (without tracking impression here)
|
|
103
120
|
*/
|
|
104
121
|
const fetchAd = useCallback(async () => {
|
|
105
122
|
if (!isInitialized) return;
|
|
@@ -107,6 +124,8 @@ export const BannerAd: React.FC<AdBannerProps> = ({
|
|
|
107
124
|
try {
|
|
108
125
|
setIsLoading(true);
|
|
109
126
|
setError(null);
|
|
127
|
+
setHasImpression(false);
|
|
128
|
+
setHasView(false);
|
|
110
129
|
|
|
111
130
|
const response = await Adgeist.fetchCreative(
|
|
112
131
|
apiKey,
|
|
@@ -119,24 +138,12 @@ export const BannerAd: React.FC<AdBannerProps> = ({
|
|
|
119
138
|
const creative: { data: AdData } = response as { data: AdData };
|
|
120
139
|
setAdData(creative.data);
|
|
121
140
|
onAdLoadSuccess?.(creative.data);
|
|
122
|
-
|
|
123
|
-
// if (creative.data.seatBid.length > 0) {
|
|
124
|
-
// await Adgeist.sendCreativeAnalytic(
|
|
125
|
-
// creative.data.seatBid?.[0]?.bid?.[0]?.id || '',
|
|
126
|
-
// dataAdSlot,
|
|
127
|
-
// publisherId,
|
|
128
|
-
// 'IMPRESSION',
|
|
129
|
-
// domain,
|
|
130
|
-
// apiKey,
|
|
131
|
-
// creative.data.id,
|
|
132
|
-
// isTestEnvironment
|
|
133
|
-
// );
|
|
134
|
-
// }
|
|
135
141
|
} catch (err) {
|
|
136
142
|
const error = err instanceof Error ? err : new Error('Ad load failed');
|
|
137
143
|
setError(error);
|
|
144
|
+
setHasImpression(false);
|
|
145
|
+
setHasView(false);
|
|
138
146
|
onAdLoadError?.(error);
|
|
139
|
-
console.error('Ad load failed:', error);
|
|
140
147
|
} finally {
|
|
141
148
|
setIsLoading(false);
|
|
142
149
|
}
|
|
@@ -152,43 +159,204 @@ export const BannerAd: React.FC<AdBannerProps> = ({
|
|
|
152
159
|
]);
|
|
153
160
|
|
|
154
161
|
/**
|
|
155
|
-
*
|
|
162
|
+
* Tracks impression when media (image/video) is fully loaded
|
|
156
163
|
*/
|
|
157
|
-
const
|
|
158
|
-
if (
|
|
159
|
-
const bidId = adData.seatBid[0]?.bid[0]?.id;
|
|
160
|
-
if (!bidId) return;
|
|
164
|
+
const trackImpressionOnMediaLoad = useCallback(async () => {
|
|
165
|
+
if (hasImpression || !bidId || !campaignId) return;
|
|
161
166
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
try {
|
|
168
|
+
const renderTime = Date.now() - renderStartTime.current;
|
|
169
|
+
|
|
170
|
+
await Adgeist.trackImpression(
|
|
171
|
+
campaignId,
|
|
172
|
+
adSpaceId,
|
|
173
|
+
publisherId,
|
|
174
|
+
apiKey,
|
|
175
|
+
bidId,
|
|
176
|
+
isTestEnvironment,
|
|
177
|
+
renderTime
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
setHasImpression(true);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.error('Failed to track impression:', err);
|
|
183
|
+
}
|
|
184
|
+
}, [
|
|
185
|
+
hasImpression,
|
|
186
|
+
bidId,
|
|
187
|
+
campaignId,
|
|
188
|
+
adSpaceId,
|
|
189
|
+
publisherId,
|
|
190
|
+
apiKey,
|
|
191
|
+
isTestEnvironment,
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Tracks view event for ads when visible for >=1s (banner) or >=2s (video) and >=50% in viewport
|
|
196
|
+
*/
|
|
197
|
+
const trackView = useCallback(async () => {
|
|
198
|
+
if (hasView || !hasImpression || !bidId || !campaignId) return;
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
const currentViewTime = viewTime.current;
|
|
202
|
+
const currentTimeToVisible = timeToVisible.current || 0;
|
|
203
|
+
const visibility = currentVisibilityRatio.current;
|
|
204
|
+
|
|
205
|
+
if (
|
|
206
|
+
currentViewTime >= (dataSlotType === 'video' ? 2000 : 1000) &&
|
|
207
|
+
visibility >= 0.5 &&
|
|
208
|
+
timeToVisible.current !== null
|
|
209
|
+
) {
|
|
210
|
+
await Adgeist.trackView(
|
|
211
|
+
campaignId,
|
|
212
|
+
adSpaceId,
|
|
166
213
|
publisherId,
|
|
167
|
-
'CLICK',
|
|
168
|
-
domain,
|
|
169
214
|
apiKey,
|
|
170
|
-
|
|
171
|
-
isTestEnvironment
|
|
215
|
+
bidId,
|
|
216
|
+
isTestEnvironment,
|
|
217
|
+
currentViewTime,
|
|
218
|
+
visibility,
|
|
219
|
+
0,
|
|
220
|
+
currentTimeToVisible
|
|
172
221
|
);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
222
|
+
|
|
223
|
+
setHasView(true);
|
|
224
|
+
}
|
|
225
|
+
} catch (err) {
|
|
226
|
+
console.error('Failed to track view:', err);
|
|
227
|
+
}
|
|
228
|
+
}, [
|
|
229
|
+
hasView,
|
|
230
|
+
hasImpression,
|
|
231
|
+
bidId,
|
|
232
|
+
campaignId,
|
|
233
|
+
adSpaceId,
|
|
234
|
+
publisherId,
|
|
235
|
+
apiKey,
|
|
236
|
+
isTestEnvironment,
|
|
237
|
+
dataSlotType,
|
|
238
|
+
]);
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Toggles play/pause state manually
|
|
242
|
+
*/
|
|
243
|
+
const togglePlayPause = useCallback(() => {
|
|
244
|
+
setIsManuallyControlled(true);
|
|
245
|
+
setIsPaused((prev) => !prev);
|
|
246
|
+
}, []);
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Calculates visibility ratio, updates view metrics, and controls video playback
|
|
250
|
+
*/
|
|
251
|
+
const checkVisibility = useCallback(() => {
|
|
252
|
+
if (!adRef.current || !hasImpression) return;
|
|
253
|
+
|
|
254
|
+
adRef.current.measure((_x, _y, _width, height, _pageX, pageY) => {
|
|
255
|
+
const window = Dimensions.get('window');
|
|
256
|
+
const windowHeight = window.height;
|
|
257
|
+
|
|
258
|
+
const adTop = pageY;
|
|
259
|
+
const adBottom = pageY + height;
|
|
260
|
+
const windowTop = 0;
|
|
261
|
+
const windowBottom = windowHeight;
|
|
262
|
+
|
|
263
|
+
const visibleTop = Math.max(adTop, windowTop);
|
|
264
|
+
const visibleBottom = Math.min(adBottom, windowBottom);
|
|
265
|
+
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
|
|
266
|
+
const visibilityRatio = height > 0 ? visibleHeight / height : 0;
|
|
267
|
+
currentVisibilityRatio.current = visibilityRatio;
|
|
268
|
+
|
|
269
|
+
const currentTime = Date.now();
|
|
270
|
+
const deltaTime = currentTime - lastCheckTime.current;
|
|
271
|
+
lastCheckTime.current = currentTime;
|
|
272
|
+
|
|
273
|
+
if (visibilityRatio >= 0.5 && timeToVisible.current === null) {
|
|
274
|
+
timeToVisible.current = currentTime - renderStartTime.current;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (visibilityRatio >= 0.5) {
|
|
278
|
+
viewTime.current += deltaTime;
|
|
279
|
+
if (!visibilityStartTime.current) {
|
|
280
|
+
visibilityStartTime.current = currentTime;
|
|
281
|
+
}
|
|
282
|
+
if (dataSlotType === 'video' && isPaused && !isManuallyControlled) {
|
|
283
|
+
setIsPaused(false);
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
visibilityStartTime.current = null;
|
|
287
|
+
if (dataSlotType === 'video' && !isPaused) {
|
|
288
|
+
setIsPaused(true);
|
|
289
|
+
setIsManuallyControlled(false);
|
|
290
|
+
}
|
|
176
291
|
}
|
|
292
|
+
|
|
293
|
+
// Only check for view tracking if view event hasn't been tracked yet
|
|
294
|
+
if (
|
|
295
|
+
!hasView &&
|
|
296
|
+
viewTime.current >= (dataSlotType === 'video' ? 2000 : 1000) &&
|
|
297
|
+
visibilityRatio >= 0.5
|
|
298
|
+
) {
|
|
299
|
+
trackView();
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}, [
|
|
303
|
+
hasImpression,
|
|
304
|
+
trackView,
|
|
305
|
+
dataSlotType,
|
|
306
|
+
isPaused,
|
|
307
|
+
hasView,
|
|
308
|
+
isManuallyControlled,
|
|
309
|
+
]);
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Handles ad click and sends click analytics
|
|
313
|
+
*/
|
|
314
|
+
const handleClick = useCallback(async () => {
|
|
315
|
+
if (!adData || !adData.seatBid.length || !bidId || !campaignId) return;
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
Adgeist.trackClick(
|
|
319
|
+
campaignId,
|
|
320
|
+
adSpaceId,
|
|
321
|
+
publisherId,
|
|
322
|
+
apiKey,
|
|
323
|
+
bidId,
|
|
324
|
+
isTestEnvironment
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
await Linking.openURL(normalizeUrl(creativeData.ctaUrl));
|
|
328
|
+
} catch (err) {
|
|
329
|
+
console.error('Failed to handle ad click:', err);
|
|
177
330
|
}
|
|
178
331
|
}, [
|
|
179
332
|
adData,
|
|
180
|
-
|
|
333
|
+
bidId,
|
|
334
|
+
campaignId,
|
|
335
|
+
adSpaceId,
|
|
181
336
|
publisherId,
|
|
182
|
-
domain,
|
|
183
337
|
apiKey,
|
|
184
338
|
isTestEnvironment,
|
|
185
339
|
creativeData,
|
|
186
340
|
]);
|
|
187
341
|
|
|
188
342
|
useEffect(() => {
|
|
343
|
+
renderStartTime.current = Date.now();
|
|
344
|
+
lastCheckTime.current = Date.now();
|
|
189
345
|
fetchAd();
|
|
190
346
|
}, [fetchAd]);
|
|
191
347
|
|
|
348
|
+
useEffect(() => {
|
|
349
|
+
if (!hasImpression) return;
|
|
350
|
+
|
|
351
|
+
if (dataSlotType === 'banner' && hasView) return;
|
|
352
|
+
|
|
353
|
+
const intervalId = setInterval(checkVisibility, 200);
|
|
354
|
+
|
|
355
|
+
return () => {
|
|
356
|
+
clearInterval(intervalId);
|
|
357
|
+
};
|
|
358
|
+
}, [hasImpression, checkVisibility, dataSlotType, hasView]);
|
|
359
|
+
|
|
192
360
|
if (isLoading) {
|
|
193
361
|
return (
|
|
194
362
|
<View
|
|
@@ -211,43 +379,69 @@ export const BannerAd: React.FC<AdBannerProps> = ({
|
|
|
211
379
|
style={{ width: '100%', height: '100%' }}
|
|
212
380
|
>
|
|
213
381
|
<View
|
|
382
|
+
ref={adRef}
|
|
214
383
|
style={[
|
|
215
384
|
styles.adContainer,
|
|
216
385
|
!isResponsive && { width: width, height: height },
|
|
217
386
|
]}
|
|
218
387
|
>
|
|
219
388
|
{dataSlotType === 'banner' ? (
|
|
220
|
-
<
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
accessibilityLabel="Ad Creative"
|
|
224
|
-
resizeMode="contain"
|
|
225
|
-
onError={(e) =>
|
|
226
|
-
console.error('Image load error:', e.nativeEvent.error)
|
|
227
|
-
}
|
|
228
|
-
/>
|
|
229
|
-
) : (
|
|
230
|
-
<View style={styles.videoCreative}>
|
|
231
|
-
<Video
|
|
389
|
+
<TouchableWithoutFeedback onPress={handleClick}>
|
|
390
|
+
<Image
|
|
391
|
+
style={styles.creative}
|
|
232
392
|
source={{ uri: creativeData.creativeUrl }}
|
|
393
|
+
accessibilityLabel="Ad Creative"
|
|
233
394
|
resizeMode="contain"
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
395
|
+
onLoad={trackImpressionOnMediaLoad}
|
|
396
|
+
onError={(e) => {
|
|
397
|
+
console.error('Image load error:', e.nativeEvent.error);
|
|
398
|
+
setError(new Error('Failed to load ad image'));
|
|
399
|
+
}}
|
|
238
400
|
/>
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
401
|
+
</TouchableWithoutFeedback>
|
|
402
|
+
) : (
|
|
403
|
+
<View style={styles.videoCreative}>
|
|
404
|
+
<TouchableWithoutFeedback onPress={handleClick}>
|
|
405
|
+
<Video
|
|
406
|
+
ref={videoRef}
|
|
407
|
+
source={{ uri: creativeData.creativeUrl }}
|
|
408
|
+
resizeMode="contain"
|
|
409
|
+
style={{ width: '100%', height: '100%' }}
|
|
410
|
+
repeat={true}
|
|
411
|
+
muted={isMuted}
|
|
412
|
+
paused={isPaused}
|
|
413
|
+
onLoad={trackImpressionOnMediaLoad}
|
|
414
|
+
onError={() => {
|
|
415
|
+
setError(new Error('Failed to load ad video'));
|
|
247
416
|
}}
|
|
248
|
-
accessibilityLabel={isMuted ? 'Unmute video' : 'Mute video'}
|
|
249
417
|
/>
|
|
250
418
|
</TouchableWithoutFeedback>
|
|
419
|
+
<View style={styles.videoControls}>
|
|
420
|
+
<TouchableWithoutFeedback onPress={togglePlayPause}>
|
|
421
|
+
<Image
|
|
422
|
+
style={styles.playPauseIcon}
|
|
423
|
+
source={{
|
|
424
|
+
uri: isPaused
|
|
425
|
+
? 'https://d2cfeg6k9cklz9.cloudfront.net/ad-icons/Play.png'
|
|
426
|
+
: 'https://d2cfeg6k9cklz9.cloudfront.net/ad-icons/Pause.png',
|
|
427
|
+
}}
|
|
428
|
+
accessibilityLabel={
|
|
429
|
+
isPaused ? 'Paused video' : 'Playing video'
|
|
430
|
+
}
|
|
431
|
+
/>
|
|
432
|
+
</TouchableWithoutFeedback>
|
|
433
|
+
<TouchableWithoutFeedback onPress={() => setIsMuted(!isMuted)}>
|
|
434
|
+
<Image
|
|
435
|
+
style={styles.soundIcon}
|
|
436
|
+
source={{
|
|
437
|
+
uri: isMuted
|
|
438
|
+
? 'https://d2cfeg6k9cklz9.cloudfront.net/ad-icons/Muted.png'
|
|
439
|
+
: 'https://d2cfeg6k9cklz9.cloudfront.net/ad-icons/Unmuted.png',
|
|
440
|
+
}}
|
|
441
|
+
accessibilityLabel={isMuted ? 'Unmute video' : 'Mute video'}
|
|
442
|
+
/>
|
|
443
|
+
</TouchableWithoutFeedback>
|
|
444
|
+
</View>
|
|
251
445
|
</View>
|
|
252
446
|
)}
|
|
253
447
|
|
|
@@ -256,7 +450,6 @@ export const BannerAd: React.FC<AdBannerProps> = ({
|
|
|
256
450
|
<Text style={styles.title} numberOfLines={1} ellipsizeMode="tail">
|
|
257
451
|
{creativeData.creativeTitle}
|
|
258
452
|
</Text>
|
|
259
|
-
|
|
260
453
|
<Text
|
|
261
454
|
style={styles.description}
|
|
262
455
|
numberOfLines={1}
|
|
@@ -264,20 +457,21 @@ export const BannerAd: React.FC<AdBannerProps> = ({
|
|
|
264
457
|
>
|
|
265
458
|
{creativeData.creativeDescription}
|
|
266
459
|
</Text>
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
460
|
+
{creativeData?.creativeBrandName && (
|
|
461
|
+
<Text
|
|
462
|
+
style={styles.brandName}
|
|
463
|
+
numberOfLines={1}
|
|
464
|
+
ellipsizeMode="tail"
|
|
465
|
+
>
|
|
466
|
+
{creativeData?.creativeBrandName || 'The Brand Name'}
|
|
467
|
+
</Text>
|
|
468
|
+
)}
|
|
275
469
|
</View>
|
|
276
470
|
<TouchableWithoutFeedback onPress={handleClick}>
|
|
277
471
|
<Image
|
|
278
472
|
style={styles.linkButton}
|
|
279
473
|
source={{
|
|
280
|
-
uri: 'https://d2cfeg6k9cklz9.cloudfront.net/
|
|
474
|
+
uri: 'https://d2cfeg6k9cklz9.cloudfront.net/ad-icons/Button.png',
|
|
281
475
|
}}
|
|
282
476
|
accessibilityLabel="Visit Advertiser Site"
|
|
283
477
|
/>
|
|
@@ -310,6 +504,14 @@ const styles = StyleSheet.create({
|
|
|
310
504
|
width: '100%',
|
|
311
505
|
height: '70%',
|
|
312
506
|
},
|
|
507
|
+
videoControls: {},
|
|
508
|
+
playPauseIcon: {
|
|
509
|
+
position: 'absolute',
|
|
510
|
+
bottom: 8,
|
|
511
|
+
left: 10,
|
|
512
|
+
width: 30,
|
|
513
|
+
height: 30,
|
|
514
|
+
},
|
|
313
515
|
soundIcon: {
|
|
314
516
|
position: 'absolute',
|
|
315
517
|
bottom: 10,
|
|
@@ -324,31 +526,35 @@ const styles = StyleSheet.create({
|
|
|
324
526
|
flexDirection: 'row',
|
|
325
527
|
justifyContent: 'space-between',
|
|
326
528
|
paddingVertical: 10,
|
|
327
|
-
|
|
529
|
+
paddingRight: 10,
|
|
530
|
+
paddingLeft: 20,
|
|
328
531
|
alignItems: 'center',
|
|
329
532
|
borderBottomLeftRadius: 5,
|
|
330
533
|
borderBottomRightRadius: 5,
|
|
331
534
|
},
|
|
332
535
|
contentContainer: {
|
|
333
|
-
|
|
536
|
+
flex: 1,
|
|
537
|
+
marginRight: 10,
|
|
334
538
|
},
|
|
335
539
|
title: {
|
|
336
540
|
color: '#1A1A1A',
|
|
337
|
-
fontSize:
|
|
541
|
+
fontSize: 16,
|
|
338
542
|
fontWeight: '600',
|
|
339
543
|
},
|
|
340
544
|
description: {
|
|
341
545
|
color: '#4A4A4A',
|
|
342
|
-
fontSize:
|
|
546
|
+
fontSize: 15,
|
|
343
547
|
marginBottom: 4,
|
|
344
548
|
},
|
|
345
549
|
brandName: {
|
|
346
550
|
color: '#6B7280',
|
|
347
551
|
fontSize: 14,
|
|
348
|
-
textTransform: '
|
|
552
|
+
textTransform: 'capitalize',
|
|
553
|
+
opacity: 0.8,
|
|
349
554
|
},
|
|
350
555
|
linkButton: {
|
|
351
|
-
width:
|
|
556
|
+
width: 80,
|
|
352
557
|
height: 40,
|
|
558
|
+
objectFit: 'contain',
|
|
353
559
|
},
|
|
354
560
|
});
|
package/src/utilities.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes a URL to ensure it has a valid protocol
|
|
3
|
+
*/
|
|
4
|
+
export const normalizeUrl = (url: string) => {
|
|
5
|
+
if (!url) return url;
|
|
6
|
+
if (url.startsWith('www.')) {
|
|
7
|
+
return `https://${url}`;
|
|
8
|
+
}
|
|
9
|
+
if (!url.match(/^[a-zA-Z]+:\/\//)) {
|
|
10
|
+
return `https://${url}`;
|
|
11
|
+
}
|
|
12
|
+
return url;
|
|
13
|
+
};
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.withRNAdgeistMainApplication = void 0;
|
|
4
|
-
exports.ktFileUpdater = ktFileUpdater;
|
|
5
|
-
const config_plugins_1 = require("@expo/config-plugins");
|
|
6
|
-
const generateCode_1 = require("@expo/config-plugins/build/utils/generateCode");
|
|
7
|
-
const withRNAdgeistMainApplication = (config) => {
|
|
8
|
-
return (0, config_plugins_1.withAppBuildGradle)((0, config_plugins_1.withMainApplication)(config, readMainApplicationFileAndUpdateContents), readBuildGradleFileAndUpdateContents);
|
|
9
|
-
};
|
|
10
|
-
exports.withRNAdgeistMainApplication = withRNAdgeistMainApplication;
|
|
11
|
-
// 1. MainActivity Modifications
|
|
12
|
-
async function readMainApplicationFileAndUpdateContents(config) {
|
|
13
|
-
const { modResults: mainApplicationFile } = config;
|
|
14
|
-
const worker = getCompatibleFileUpdater(mainApplicationFile.language);
|
|
15
|
-
mainApplicationFile.contents = worker(mainApplicationFile.contents);
|
|
16
|
-
return config;
|
|
17
|
-
}
|
|
18
|
-
function readBuildGradleFileAndUpdateContents(config) {
|
|
19
|
-
const { modResults } = config;
|
|
20
|
-
if (!modResults.contents.includes('implementation "ai.adgeist:adgeistkit:')) {
|
|
21
|
-
modResults.contents = modResults.contents.replace(/dependencies\s*{/, `dependencies {
|
|
22
|
-
implementation "ai.adgeist:adgeistkit:0.0.1" // AdgeistKit Dependency`);
|
|
23
|
-
}
|
|
24
|
-
return config;
|
|
25
|
-
}
|
|
26
|
-
function getCompatibleFileUpdater(language) {
|
|
27
|
-
switch (language) {
|
|
28
|
-
case 'kt':
|
|
29
|
-
return ktFileUpdater;
|
|
30
|
-
default:
|
|
31
|
-
throw new Error(`Cannot add React Native Orientation Director code to MainActivity of language "${language}"`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function ktFileUpdater(originalContents) {
|
|
35
|
-
// Safer anchor detection
|
|
36
|
-
const anchors = [
|
|
37
|
-
/super\.onCreate\(/,
|
|
38
|
-
/@Override\s+fun onCreate\(/,
|
|
39
|
-
/class \w+ : ReactActivity/,
|
|
40
|
-
].find((anchor) => anchor.test(originalContents));
|
|
41
|
-
if (!anchors) {
|
|
42
|
-
throw new Error('Could not find suitable insertion point in MainActivity');
|
|
43
|
-
}
|
|
44
|
-
const packageImportCodeBlock = 'import com.adgeist.AdgeistPackage';
|
|
45
|
-
const rightBeforeClassDeclaration = /import com.facebook.react.ReactPackage/;
|
|
46
|
-
const importMergeResults = (0, generateCode_1.mergeContents)({
|
|
47
|
-
tag: '@react-native-adgeist/package-import',
|
|
48
|
-
src: originalContents,
|
|
49
|
-
newSrc: packageImportCodeBlock,
|
|
50
|
-
anchor: rightBeforeClassDeclaration,
|
|
51
|
-
offset: 0,
|
|
52
|
-
comment: '// React Native Adgeist',
|
|
53
|
-
});
|
|
54
|
-
const onConfigurationChangedCodeBlock = `packages.add(AdgeistPackage())`;
|
|
55
|
-
const rightBeforeOnReturnStatement = /return packages/;
|
|
56
|
-
const implementationMergeResults = (0, generateCode_1.mergeContents)({
|
|
57
|
-
tag: '@react-native-adgeist/package-initialization',
|
|
58
|
-
src: importMergeResults.contents,
|
|
59
|
-
newSrc: onConfigurationChangedCodeBlock,
|
|
60
|
-
anchor: rightBeforeOnReturnStatement,
|
|
61
|
-
offset: 0,
|
|
62
|
-
comment: '// Package Initialization',
|
|
63
|
-
});
|
|
64
|
-
return implementationMergeResults.contents;
|
|
65
|
-
}
|
package/plugin/build/index.d.ts
DELETED