@adadapted/react-native-sdk 3.1.12 → 3.3.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.
Files changed (75) hide show
  1. package/android/.project +1 -1
  2. package/android/.settings/org.eclipse.buildship.core.prefs +2 -2
  3. package/android/bin/build.gradle +6 -5
  4. package/android/bin/gradle.properties +4 -4
  5. package/android/build.gradle +6 -5
  6. package/android/gradle.properties +3 -3
  7. package/android/src/main/AndroidManifest.xml +1 -3
  8. package/lib/commonjs/api/adadaptedApiRequests.js.map +1 -1
  9. package/lib/commonjs/api/adadaptedApiRequests.mock.js +25 -8
  10. package/lib/commonjs/api/adadaptedApiRequests.mock.js.map +1 -1
  11. package/lib/commonjs/api/adadaptedApiTypes.js +72 -26
  12. package/lib/commonjs/api/adadaptedApiTypes.js.map +1 -1
  13. package/lib/commonjs/components/AdZone.js +200 -129
  14. package/lib/commonjs/components/AdZone.js.map +1 -1
  15. package/lib/commonjs/components/ReportAdButton.js +38 -0
  16. package/lib/commonjs/components/ReportAdButton.js.map +1 -0
  17. package/lib/commonjs/images/ReportIcon.png +0 -0
  18. package/lib/commonjs/index.js +162 -78
  19. package/lib/commonjs/index.js.map +1 -1
  20. package/lib/commonjs/types.js +4 -0
  21. package/lib/commonjs/types.js.map +1 -1
  22. package/lib/commonjs/util.js +13 -0
  23. package/lib/commonjs/util.js.map +1 -1
  24. package/lib/module/api/adadaptedApiRequests.js.map +1 -1
  25. package/lib/module/api/adadaptedApiRequests.mock.js +25 -9
  26. package/lib/module/api/adadaptedApiRequests.mock.js.map +1 -1
  27. package/lib/module/api/adadaptedApiTypes.js +70 -24
  28. package/lib/module/api/adadaptedApiTypes.js.map +1 -1
  29. package/lib/module/components/AdZone.js +192 -128
  30. package/lib/module/components/AdZone.js.map +1 -1
  31. package/lib/module/components/ReportAdButton.js +30 -0
  32. package/lib/module/components/ReportAdButton.js.map +1 -0
  33. package/lib/module/images/ReportIcon.png +0 -0
  34. package/lib/module/index.js +162 -77
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/module/types.js +1 -1
  37. package/lib/module/types.js.map +1 -1
  38. package/lib/module/util.js +14 -2
  39. package/lib/module/util.js.map +1 -1
  40. package/lib/typescript/example/index.d.ts +1 -0
  41. package/lib/typescript/example/index.d.ts.map +1 -0
  42. package/lib/typescript/example/src/App.d.ts +15 -62
  43. package/lib/typescript/example/src/App.d.ts.map +1 -0
  44. package/lib/typescript/example/src/OffScreenAdZone.d.ts +38 -0
  45. package/lib/typescript/example/src/OffScreenAdZone.d.ts.map +1 -0
  46. package/lib/typescript/example/src/StandardAdZone.d.ts +38 -0
  47. package/lib/typescript/example/src/StandardAdZone.d.ts.map +1 -0
  48. package/lib/typescript/index.d.ts +2 -0
  49. package/lib/typescript/index.d.ts.map +1 -0
  50. package/lib/typescript/jest.setup.d.ts +2 -1
  51. package/lib/typescript/jest.setup.d.ts.map +1 -0
  52. package/lib/typescript/src/api/adadaptedApiRequests.d.ts +1 -0
  53. package/lib/typescript/src/api/adadaptedApiRequests.d.ts.map +1 -0
  54. package/lib/typescript/src/api/adadaptedApiRequests.mock.d.ts +1 -0
  55. package/lib/typescript/src/api/adadaptedApiRequests.mock.d.ts.map +1 -0
  56. package/lib/typescript/src/api/adadaptedApiTypes.d.ts +9 -0
  57. package/lib/typescript/src/api/adadaptedApiTypes.d.ts.map +1 -0
  58. package/lib/typescript/src/components/AdZone.d.ts +9 -80
  59. package/lib/typescript/src/components/AdZone.d.ts.map +1 -0
  60. package/lib/typescript/src/components/ReportAdButton.d.ts +19 -0
  61. package/lib/typescript/src/components/ReportAdButton.d.ts.map +1 -0
  62. package/lib/typescript/src/index.d.ts +39 -1
  63. package/lib/typescript/src/index.d.ts.map +1 -0
  64. package/lib/typescript/src/types.d.ts +3 -2
  65. package/lib/typescript/src/types.d.ts.map +1 -0
  66. package/lib/typescript/src/util.d.ts +13 -0
  67. package/lib/typescript/src/util.d.ts.map +1 -0
  68. package/package.json +155 -155
  69. package/src/api/adadaptedApiRequests.mock.ts +25 -9
  70. package/src/api/adadaptedApiTypes.ts +8 -0
  71. package/src/components/AdZone.tsx +222 -173
  72. package/src/components/ReportAdButton.tsx +46 -0
  73. package/src/images/ReportIcon.png +0 -0
  74. package/src/index.tsx +160 -56
  75. package/src/util.ts +13 -0
@@ -3,7 +3,13 @@
3
3
  * @module
4
4
  */
5
5
  import * as React from "react";
6
- import { Linking, StyleSheet, View, ViewStyle } from "react-native";
6
+ import {
7
+ DeviceEventEmitter,
8
+ Linking,
9
+ StyleSheet,
10
+ View,
11
+ ViewStyle,
12
+ } from "react-native";
7
13
  import * as adadaptedApiRequests from "../api/adadaptedApiRequests";
8
14
  import {
9
15
  Ad,
@@ -15,6 +21,8 @@ import {
15
21
  import { WebView } from "react-native-webview";
16
22
  import { ApiEnv, DeviceOS } from "../index";
17
23
  import { safeInvoke } from "../util";
24
+ import { useEffect, useState } from "react";
25
+ import { ReportAdButton } from "./ReportAdButton";
18
26
 
19
27
  /**
20
28
  * Props interface for {@link AdZone}.
@@ -58,20 +66,14 @@ interface Props {
58
66
  * @param items - The array of items to "add to list".
59
67
  */
60
68
  onAddToListTriggered?(items: DetailedListItem[]): void;
61
- }
62
-
63
- /**
64
- * State interface for {@link AdZone}.
65
- */
66
- interface State {
67
69
  /**
68
- * Tracks the current ad index being shown.
70
+ * Is the ad zone visible on render.
69
71
  */
70
- adIndexShown: number;
72
+ offScreenAdZone: boolean;
71
73
  /**
72
- * Tracks the coordinates when the user started touching the Ad View.
74
+ * Track the ad zone visibility in parent component. (for off-screen ads)
73
75
  */
74
- touchStartCoords: TouchCoordinates | undefined;
76
+ isAdZoneVisible?: boolean;
75
77
  }
76
78
 
77
79
  /**
@@ -100,157 +102,174 @@ interface StyleDef {
100
102
  * Styles for the WebView element.
101
103
  */
102
104
  webView: ViewStyle;
105
+ /**
106
+ * Styles for the ReportAdButton.
107
+ */
108
+ reportAd: ViewStyle;
103
109
  }
110
+ /**
111
+ * Timer used for cycling through ads in the zone
112
+ * based on the ad "refresh time" for each ad.
113
+ */
114
+ let cycleAdTimer: ReturnType<typeof setTimeout> | undefined;
104
115
 
105
116
  /**
106
117
  * Creates the AdZone component.
118
+ * @param props - properties passed to AdZone.
119
+ * @returns an AdZone JSX Element.
107
120
  */
108
- export class AdZone extends React.Component<Props, State> {
121
+ export function AdZone(props: Props): JSX.Element {
122
+ // Generates a random number between 0 and (number of available ads - 1).
123
+ const startingAdIndex = Math.floor(
124
+ Math.random() * props.adZoneData.ads.length
125
+ );
126
+
109
127
  /**
110
- * Timer used for cycling through ads in the zone
111
- * based on the ad "refresh time" for each ad.
128
+ * Tracks the current ad index being shown.
112
129
  */
113
- private cycleAdTimer: ReturnType<typeof setTimeout> | undefined;
114
-
130
+ const [adIndexShown, setAdIndexShown] = useState(startingAdIndex);
131
+ /**
132
+ * Tracks the coordinates when the user started touching the Ad View.
133
+ */
134
+ const [touchStartCoords, setTouchStartCoords] = useState({ x: 0, y: 0 });
115
135
  /**
116
- * @inheritDoc
136
+ * Track ad visibility (for off-screen ads).
117
137
  */
118
- constructor(props: Props, context?: any) {
119
- super(props, context);
138
+ const [isAdVisible, setIsAdVisibile] = useState(props.isAdZoneVisible);
139
+
140
+ // Setup device listeners.
141
+ useEffect(() => {
142
+ DeviceEventEmitter.addListener("visibility-event", (event: boolean) => {
143
+ setIsAdVisibile(event);
144
+ });
145
+
146
+ DeviceEventEmitter.addListener("acknowledge", (itemName: string) => {
147
+ acknowledge(itemName);
148
+ });
120
149
 
121
- // Generates a random number between 0 and (number of available ads - 1).
122
- const startingAdIndex = Math.floor(
123
- Math.random() * this.props.adZoneData.ads.length
124
- );
150
+ if (props.offScreenAdZone && isAdVisible) {
151
+ sendAdImpression();
152
+ } else if (!props.offScreenAdZone) {
153
+ sendAdImpression();
154
+ }
125
155
 
126
- this.state = {
127
- adIndexShown: startingAdIndex,
128
- touchStartCoords: undefined,
156
+ return () => {
157
+ clearTimeout(cycleAdTimer);
158
+ DeviceEventEmitter.removeAllListeners("visibility-event");
159
+ DeviceEventEmitter.removeAllListeners("acknowledge");
129
160
  };
130
- }
161
+ // eslint-disable-next-line react-hooks/exhaustive-deps
162
+ }, []);
131
163
 
132
- /**
133
- * @inheritDoc
134
- */
135
- public componentDidMount(): void {
136
- this.initializeAd();
137
- }
164
+ // Send impression on ad cycle.
165
+ useEffect(() => {
166
+ startAdTimer();
167
+ if (props.offScreenAdZone && isAdVisible) {
168
+ sendAdImpression();
169
+ } else if (!props.offScreenAdZone) {
170
+ sendAdImpression();
171
+ }
172
+ // eslint-disable-next-line react-hooks/exhaustive-deps
173
+ }, [adIndexShown]);
138
174
 
139
- /**
140
- * @inheritDoc
141
- */
142
- public componentWillUnmount(): void {
143
- if (this.cycleAdTimer) {
144
- clearTimeout(this.cycleAdTimer);
175
+ // Send impression based on visibility change. (for off-screen ads)
176
+ useEffect(() => {
177
+ if (props.offScreenAdZone && isAdVisible) {
178
+ sendAdImpression();
179
+ } else if (!props.offScreenAdZone) {
180
+ sendAdImpression();
145
181
  }
146
- }
182
+ // eslint-disable-next-line react-hooks/exhaustive-deps
183
+ }, [isAdVisible]);
147
184
 
148
185
  /**
149
- * @inheritDoc
186
+ * Generates all component related styles.
187
+ * @returns the styles needed for the component.
150
188
  */
151
- public render(): JSX.Element {
152
- // Generate the styles each render in case the ad is updated with
153
- // new settings that need to be reflected in the styles of the view.
154
- const styles = this.generateStyles();
155
- const currentAd: Ad | undefined =
156
- this.props.adZoneData.ads[this.state.adIndexShown] || undefined;
157
- const finalMainViewStyle = styles.mainView;
158
-
159
- if (!currentAd || !currentAd.creative_url) {
160
- // If there is no ad to display, make the view take up no space.
161
- finalMainViewStyle.width = 0;
162
- finalMainViewStyle.height = 0;
163
- }
164
-
165
- return (
166
- <View style={finalMainViewStyle}>
167
- {currentAd && currentAd.creative_url ? (
168
- <WebView
169
- source={{
170
- uri: currentAd.creative_url,
171
- }}
172
- androidLayerType="hardware"
173
- automaticallyAdjustContentInsets={false}
174
- style={styles.webView}
175
- onTouchStart={(e) => {
176
- this.setState({
177
- touchStartCoords: {
178
- x: e.nativeEvent.pageX,
179
- y: e.nativeEvent.pageY,
180
- },
181
- });
182
- }}
183
- onTouchEnd={(e) => {
184
- if (this.state.touchStartCoords) {
185
- const touchEndCoords: TouchCoordinates = {
186
- x: e.nativeEvent.pageX,
187
- y: e.nativeEvent.pageY,
188
- };
189
+ function generateStyles(): StyleDef {
190
+ return StyleSheet.create({
191
+ mainView: {
192
+ width: "100%",
193
+ height: "100%",
194
+ },
195
+ webView: {
196
+ width: "100%",
197
+ height: "100%",
198
+ },
199
+ reportAd: {
200
+ position: "absolute",
201
+ top: 10,
202
+ right: 10,
203
+ },
204
+ });
205
+ }
189
206
 
190
- if (
191
- Math.abs(
192
- this.state.touchStartCoords.x -
193
- touchEndCoords.x
194
- ) < this.props.xyDragDistanceAllowed &&
195
- Math.abs(
196
- this.state.touchStartCoords.y -
197
- touchEndCoords.y
198
- ) < this.props.xyDragDistanceAllowed
199
- ) {
200
- this.onAdZoneSelected(currentAd);
201
- }
207
+ // Generate the styles each render in case the ad is updated with
208
+ // new settings that need to be reflected in the styles of the view.
209
+ const styles = generateStyles();
210
+ const currentAd: Ad | undefined =
211
+ props.adZoneData.ads[adIndexShown] || undefined;
212
+ const finalMainViewStyle = styles.mainView;
202
213
 
203
- // Make sure to reset the start coords
204
- this.setState({
205
- touchStartCoords: undefined,
206
- });
207
- }
208
- }}
209
- />
210
- ) : undefined}
211
- </View>
212
- );
214
+ if (!currentAd || !currentAd.creative_url) {
215
+ // If there is no ad to display, make the view take up no space.
216
+ finalMainViewStyle.width = 0;
217
+ finalMainViewStyle.height = 0;
213
218
  }
214
219
 
215
220
  /**
216
221
  * Triggers when the user selects the ad zone.
217
- * @param currentAd - The ad currently displayed.
222
+ * @param currentlyDisplayedAd - The ad currently displayed.
218
223
  */
219
- private onAdZoneSelected(currentAd: Ad): void {
224
+ function onAdZoneSelected(currentlyDisplayedAd: Ad): void {
220
225
  // Determine the "action type" and perform that specific action.
221
226
  if (
222
- currentAd.action_type === AdActionType.EXTERNAL &&
223
- currentAd.action_path
227
+ currentlyDisplayedAd.action_type === AdActionType.EXTERNAL &&
228
+ currentlyDisplayedAd.action_path
224
229
  ) {
225
230
  // Action Type: EXTERNAL
226
- Linking.openURL(currentAd.action_path).then();
231
+ Linking.openURL(currentlyDisplayedAd.action_path).then();
227
232
  } else if (
228
- currentAd.action_type === AdActionType.CONTENT &&
229
- currentAd.payload &&
230
- currentAd.payload.detailed_list_items
233
+ currentlyDisplayedAd.action_type === AdActionType.CONTENT &&
234
+ currentlyDisplayedAd.payload &&
235
+ currentlyDisplayedAd.payload.detailed_list_items
231
236
  ) {
232
237
  safeInvoke(
233
- this.props.onAddToListTriggered,
234
- currentAd.payload.detailed_list_items
238
+ props.onAddToListTriggered,
239
+ currentlyDisplayedAd.payload.detailed_list_items
235
240
  );
236
241
  }
237
242
 
238
- this.triggerReportAdEvent(currentAd, ReportedEventType.INTERACTION);
239
- if (this.cycleAdTimer) {
240
- clearTimeout(this.cycleAdTimer);
243
+ cycleDisplayedAd();
244
+ }
245
+
246
+ /**
247
+ * Call to acknowledge ATL item(s) added to user list.
248
+ * @param itemName - Detailed list item title from ad that was clicked.
249
+ */
250
+ function acknowledge(itemName: string): void {
251
+ if (props.adZoneData.ads) {
252
+ props.adZoneData.ads.forEach((ad) => {
253
+ if (ad.action_type === "c") {
254
+ ad.payload.detailed_list_items.forEach((item) => {
255
+ if (item.product_title === itemName) {
256
+ triggerReportAdEvent(
257
+ ad,
258
+ ReportedEventType.INTERACTION
259
+ );
260
+ }
261
+ });
262
+ }
263
+ });
241
264
  }
242
- this.cycleDisplayedAd();
243
265
  }
244
266
 
245
267
  /**
246
268
  * Triggered when we need to report an ad event to the API.
247
- * @param currentAd - The ad to send an event for.
269
+ * @param ad - The ad to send an event for.
248
270
  * @param eventType - The event type for the reported event.
249
271
  */
250
- private triggerReportAdEvent(
251
- currentAd: Ad,
252
- eventType: ReportedEventType
253
- ): void {
272
+ function triggerReportAdEvent(ad: Ad, eventType: ReportedEventType): void {
254
273
  // The event timestamp has to be sent as a unix timestamp.
255
274
  const currentTs = Math.round(new Date().getTime() / 1000);
256
275
 
@@ -258,20 +277,20 @@ export class AdZone extends React.Component<Props, State> {
258
277
  adadaptedApiRequests
259
278
  .reportAdEvent(
260
279
  {
261
- app_id: this.props.appId,
262
- session_id: this.props.sessionId,
263
- udid: this.props.udid,
280
+ app_id: props.appId,
281
+ session_id: props.sessionId,
282
+ udid: props.udid,
264
283
  events: [
265
284
  {
266
- ad_id: currentAd.ad_id,
267
- impression_id: currentAd.impression_id,
285
+ ad_id: ad.ad_id,
286
+ impression_id: ad.impression_id,
268
287
  event_type: eventType,
269
288
  created_at: currentTs,
270
289
  },
271
290
  ],
272
291
  },
273
- this.props.deviceOs,
274
- this.props.apiEnv
292
+ props.deviceOs,
293
+ props.apiEnv
275
294
  )
276
295
  .then(() => {
277
296
  // Do nothing with the response for now...
@@ -280,69 +299,99 @@ export class AdZone extends React.Component<Props, State> {
280
299
 
281
300
  /**
282
301
  * Generates a new timer for cycling to the next ad.
283
- * @param timerLength - The length of time(in milliseconds) to initialize
284
- * the timer with.
285
302
  */
286
- private createAdTimer(timerLength: number): void {
287
- if (this.props.adZoneData.ads.length > 0) {
288
- this.cycleAdTimer = setTimeout(() => {
289
- this.cycleDisplayedAd();
290
- }, timerLength);
303
+ function startAdTimer(): void {
304
+ clearTimeout(cycleAdTimer);
305
+
306
+ if (props.adZoneData.ads.length > 0) {
307
+ const refreshTime: number =
308
+ props.adZoneData.ads[adIndexShown].refresh_time * 1000;
309
+ cycleAdTimer = setTimeout(cycleDisplayedAd, refreshTime);
291
310
  }
292
311
  }
293
312
 
294
313
  /**
295
314
  * Cycles to the next ad to display in the current available sequence of ads.
296
315
  */
297
- private cycleDisplayedAd(): void {
316
+ function cycleDisplayedAd(): void {
298
317
  // Start by determining the next ad index to display.
299
318
  let nextAdIndex = 0;
319
+ const lastAd = props.adZoneData.ads[adIndexShown];
300
320
 
301
- if (this.state.adIndexShown < this.props.adZoneData.ads.length - 1) {
302
- nextAdIndex = this.state.adIndexShown + 1;
321
+ if (adIndexShown < props.adZoneData.ads.length - 1) {
322
+ nextAdIndex = adIndexShown + 1;
303
323
  }
304
324
 
305
- this.setState(
306
- {
307
- adIndexShown: nextAdIndex,
308
- },
309
- () => {
310
- this.initializeAd();
311
- }
312
- );
325
+ if (nextAdIndex !== adIndexShown && lastAd.impression_tracked) {
326
+ // Reset ad impression tracking status.
327
+ lastAd.impression_tracked = false;
328
+ } else {
329
+ // Send invisible ad impression if ad was not visible before end of timer cycle.
330
+ triggerReportAdEvent(
331
+ lastAd,
332
+ ReportedEventType.INVISIBLE_IMPRESSION
333
+ );
334
+ }
335
+
336
+ setAdIndexShown(nextAdIndex);
313
337
  }
314
338
 
315
339
  /**
316
- * Performs all ad initialization tasks when a new ad is being displayed.
340
+ * Send ad tracking impression.
317
341
  */
318
- private initializeAd(): void {
319
- // Create the new timer based on the new ad index.
320
- this.createAdTimer(
321
- this.props.adZoneData.ads[this.state.adIndexShown].refresh_time *
322
- 1000
323
- );
342
+ function sendAdImpression(): void {
343
+ const ad = props.adZoneData.ads[adIndexShown];
324
344
 
325
- // Trigger an impression event for the ad.
326
- this.triggerReportAdEvent(
327
- this.props.adZoneData.ads[this.state.adIndexShown],
328
- ReportedEventType.IMPRESSION
329
- );
345
+ if (!ad.impression_tracked) {
346
+ triggerReportAdEvent(ad, ReportedEventType.IMPRESSION);
347
+ ad.impression_tracked = true;
348
+ }
330
349
  }
331
350
 
332
- /**
333
- * Generates all component related styles.
334
- * @returns the styles needed for the component.
335
- */
336
- private generateStyles(): StyleDef {
337
- return StyleSheet.create({
338
- mainView: {
339
- width: "100%",
340
- height: "100%",
341
- },
342
- webView: {
343
- width: "100%",
344
- height: "100%",
345
- },
346
- });
347
- }
351
+ // Returned JSX.
352
+ return (
353
+ <View style={finalMainViewStyle}>
354
+ {currentAd && currentAd.creative_url ? (
355
+ <WebView
356
+ source={{
357
+ uri: currentAd.creative_url,
358
+ }}
359
+ androidLayerType="hardware"
360
+ automaticallyAdjustContentInsets={false}
361
+ style={styles.webView}
362
+ onTouchStart={(e) => {
363
+ setTouchStartCoords({
364
+ x: e.nativeEvent.pageX,
365
+ y: e.nativeEvent.pageY,
366
+ });
367
+ }}
368
+ onTouchEnd={(e) => {
369
+ if (touchStartCoords) {
370
+ const touchEndCoords: TouchCoordinates = {
371
+ x: e.nativeEvent.pageX,
372
+ y: e.nativeEvent.pageY,
373
+ };
374
+
375
+ if (
376
+ Math.abs(
377
+ touchStartCoords.x - touchEndCoords.x
378
+ ) < props.xyDragDistanceAllowed &&
379
+ Math.abs(
380
+ touchStartCoords.y - touchEndCoords.y
381
+ ) < props.xyDragDistanceAllowed
382
+ ) {
383
+ onAdZoneSelected(currentAd);
384
+ }
385
+
386
+ // Make sure to reset the start coords
387
+ setTouchStartCoords({ x: 0, y: 0 });
388
+ }
389
+ }}
390
+ />
391
+ ) : undefined}
392
+ <View style={styles.reportAd}>
393
+ <ReportAdButton adId={currentAd.ad_id} udid={props.udid} />
394
+ </View>
395
+ </View>
396
+ );
348
397
  }
@@ -0,0 +1,46 @@
1
+ import React, { useEffect } from "react";
2
+ import { Image, Linking, StyleSheet, TouchableOpacity } from "react-native";
3
+
4
+ interface Props {
5
+ /**
6
+ * The ad id of the current ad.
7
+ */
8
+ adId: string;
9
+ /**
10
+ * The current user's udid.
11
+ */
12
+ udid: string;
13
+ }
14
+
15
+ /**
16
+ * Creates the ReportAdButton component.
17
+ * @param props The component's props.
18
+ * @returns a reportSdButton JSX Element.
19
+ */
20
+ export function ReportAdButton(props: Props): JSX.Element {
21
+ const reportAdUrlBase = new URL("https://feedback.add-it.io/");
22
+
23
+ const styles = StyleSheet.create({
24
+ buttonStyle: {
25
+ width: 14,
26
+ height: 14,
27
+ },
28
+ });
29
+
30
+ useEffect(() => {
31
+ reportAdUrlBase.searchParams.append("aid", props.adId);
32
+ reportAdUrlBase.searchParams.append("uid", props.udid);
33
+ // eslint-disable-next-line react-hooks/exhaustive-deps
34
+ }, [props]);
35
+
36
+ return (
37
+ <TouchableOpacity
38
+ style={styles.buttonStyle}
39
+ onPress={() => {
40
+ Linking.openURL(reportAdUrlBase.toString());
41
+ }}
42
+ >
43
+ <Image source={require("../images/ReportIcon.png")} />
44
+ </TouchableOpacity>
45
+ );
46
+ }
Binary file