@sellwild/react-native-sdk 1.7.4 → 1.8.1
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 +2 -2
- package/SellwildSDK-RN.podspec +3 -1
- package/android/build.gradle +3 -1
- package/android/src/main/java/com/sellwild/rnsdk/SellwildBannerViewManager.kt +44 -3
- package/android/src/main/java/com/sellwild/rnsdk/SellwildFeedViewManager.kt +71 -10
- package/android/src/main/java/com/sellwild/rnsdk/SellwildModule.kt +18 -0
- package/ios/SellwildBannerViewManager.m +1 -0
- package/ios/SellwildBannerViewManager.swift +14 -2
- package/ios/SellwildFeedViewManager.m +1 -0
- package/ios/SellwildFeedViewManager.swift +18 -11
- package/ios/SellwildRNModule.m +1 -0
- package/ios/SellwildRNModule.swift +14 -0
- package/package.json +4 -6
- package/src/SellwildBanner.tsx +15 -28
- package/src/SellwildFeed.tsx +47 -9
- package/src/appIdentity.ts +2 -3
- package/src/commands.ts +30 -1
- package/src/index.ts +7 -5
- package/src/nativeConfig.ts +34 -0
- package/src/SellwildWidget.tsx +0 -145
- package/src/htmlBuilder.ts +0 -383
package/src/SellwildFeed.tsx
CHANGED
|
@@ -23,14 +23,15 @@ import { resolveAppIdentity } from './appIdentity'
|
|
|
23
23
|
// iOS: SellwildFeedView (UITableView-backed)
|
|
24
24
|
// Android: com.sellwild.sdk.SellwildFeedView (RecyclerView-backed)
|
|
25
25
|
//
|
|
26
|
-
// This
|
|
27
|
-
// <SellwildWidget
|
|
26
|
+
// This is the one-component marketplace surface for React Native (the
|
|
27
|
+
// WebView-based <SellwildWidget> has been removed).
|
|
28
28
|
|
|
29
29
|
const NATIVE_NAME = 'SellwildFeedView'
|
|
30
30
|
|
|
31
31
|
interface NativeFeedProps {
|
|
32
32
|
config: object
|
|
33
33
|
scrollEnabled?: boolean
|
|
34
|
+
consumeListingTaps?: boolean
|
|
34
35
|
style?: StyleProp<ViewStyle>
|
|
35
36
|
onFeedLoaded?: (e: NativeSyntheticEvent<{}>) => void
|
|
36
37
|
onFeedReady?: (e: NativeSyntheticEvent<{ listingCount: number }>) => void
|
|
@@ -95,9 +96,23 @@ export interface SellwildFeedProps {
|
|
|
95
96
|
onFeedReady?: (listingCount: number) => void
|
|
96
97
|
|
|
97
98
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
99
|
+
* When `true`, listing taps are fully handled by the host app: the SDK does
|
|
100
|
+
* NOT open the listing in the in-app browser (Custom Tabs /
|
|
101
|
+
* SFSafariViewController) and only fires `onListingTap`. Use this to route
|
|
102
|
+
* listings through your own navigation. Defaults to `false` (the SDK opens
|
|
103
|
+
* the listing URL), so existing integrations are unaffected.
|
|
104
|
+
*/
|
|
105
|
+
consumeListingTaps?: boolean
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Fired when a listing card is tapped. This is a notification only: the
|
|
109
|
+
* return value is ignored, because React Native events are delivered to JS
|
|
110
|
+
* asynchronously — after the native side has already decided whether to
|
|
111
|
+
* open the browser. To handle navigation yourself, set
|
|
112
|
+
* `consumeListingTaps` instead.
|
|
113
|
+
*
|
|
114
|
+
* The `boolean` return type is kept only so existing handlers still compile;
|
|
115
|
+
* it is deprecated and has no effect.
|
|
101
116
|
*/
|
|
102
117
|
onListingTap?: (listing: SellwildListing) => boolean | void
|
|
103
118
|
|
|
@@ -117,10 +132,22 @@ export interface SellwildFeedProps {
|
|
|
117
132
|
onError?: (error: Error) => void
|
|
118
133
|
}
|
|
119
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Zone ids are `number | string` in core config, defaulting to `0` (unset).
|
|
137
|
+
* Both native bridges read them as strings, so send a string, or nothing when
|
|
138
|
+
* unset: Android threw on a number and iOS silently dropped it.
|
|
139
|
+
*/
|
|
140
|
+
function zoneIdProp(v: number | string | null | undefined): string | undefined {
|
|
141
|
+
if (v === null || v === undefined) return undefined
|
|
142
|
+
const s = String(v).trim()
|
|
143
|
+
return s === '' || s === '0' ? undefined : s
|
|
144
|
+
}
|
|
145
|
+
|
|
120
146
|
export function SellwildFeed({
|
|
121
147
|
config,
|
|
122
148
|
style,
|
|
123
149
|
scrollEnabled = true,
|
|
150
|
+
consumeListingTaps = false,
|
|
124
151
|
onContentSizeChange,
|
|
125
152
|
onLoad,
|
|
126
153
|
onFeedReady,
|
|
@@ -134,6 +161,8 @@ export function SellwildFeed({
|
|
|
134
161
|
// scrolling, so we size our own container from the native-reported content
|
|
135
162
|
// height rather than filling with flex:1.
|
|
136
163
|
const [contentHeight, setContentHeight] = React.useState<number | null>(null)
|
|
164
|
+
// Dev-only: warn once per feed about the ignored `return true`, not on every tap.
|
|
165
|
+
const warnedReturnTrue = React.useRef(false)
|
|
137
166
|
const embedded = scrollEnabled === false
|
|
138
167
|
const containerStyle: StyleProp<ViewStyle> = embedded
|
|
139
168
|
? [contentHeight != null ? { height: contentHeight } : undefined, style]
|
|
@@ -186,9 +215,9 @@ export function SellwildFeed({
|
|
|
186
215
|
remote: config.remote,
|
|
187
216
|
listingsUrl: config.listingsUrl,
|
|
188
217
|
priceColor: config.priceColor,
|
|
189
|
-
bannerZid: config.bannerZid,
|
|
190
|
-
bottomBannerZid: config.bottomBannerZid,
|
|
191
|
-
mobileBannerZid: config.mobileBannerZid,
|
|
218
|
+
bannerZid: zoneIdProp(config.bannerZid),
|
|
219
|
+
bottomBannerZid: zoneIdProp(config.bottomBannerZid),
|
|
220
|
+
mobileBannerZid: zoneIdProp(config.mobileBannerZid),
|
|
192
221
|
mobileZids: config.mobileZids,
|
|
193
222
|
}
|
|
194
223
|
|
|
@@ -197,6 +226,7 @@ export function SellwildFeed({
|
|
|
197
226
|
style={containerStyle}
|
|
198
227
|
config={nativeConfig}
|
|
199
228
|
scrollEnabled={scrollEnabled}
|
|
229
|
+
consumeListingTaps={consumeListingTaps}
|
|
200
230
|
onContentSizeChange={(e: NativeSyntheticEvent<{ width?: number; height: number }>) => {
|
|
201
231
|
const { width, height } = e.nativeEvent ?? { height: 0 }
|
|
202
232
|
// Only size our own container when embedded; a scrolling feed fills
|
|
@@ -209,7 +239,15 @@ export function SellwildFeed({
|
|
|
209
239
|
onFeedReady?.(e.nativeEvent.listingCount)
|
|
210
240
|
}}
|
|
211
241
|
onListingTap={(e: NativeSyntheticEvent<{ listing: SellwildListing }>) => {
|
|
212
|
-
onListingTap?.(e.nativeEvent.listing)
|
|
242
|
+
const result = onListingTap?.(e.nativeEvent.listing)
|
|
243
|
+
if (__DEV__ && result === true && !consumeListingTaps && !warnedReturnTrue.current) {
|
|
244
|
+
warnedReturnTrue.current = true
|
|
245
|
+
console.warn(
|
|
246
|
+
'[Sellwild] onListingTap returned true, but the return value is ignored ' +
|
|
247
|
+
'(RN events are async). Set consumeListingTaps on <SellwildFeed> to ' +
|
|
248
|
+
'stop the SDK from opening the listing.',
|
|
249
|
+
)
|
|
250
|
+
}
|
|
213
251
|
}}
|
|
214
252
|
onAdImpression={(e: NativeSyntheticEvent<{ zoneId: string }>) => {
|
|
215
253
|
onAdImpression?.(e.nativeEvent.zoneId)
|
package/src/appIdentity.ts
CHANGED
|
@@ -10,14 +10,13 @@ import type { SellwildConfig } from '@sellwild/sdk-core'
|
|
|
10
10
|
* (OS-agnostic) keys onto `appBundleId`/`appStoreUrl` and stashes the raw
|
|
11
11
|
* payload on `config.remote`; the platform is only known here on the device,
|
|
12
12
|
* so we resolve the OS-specific value at the bridge boundary — the RN analog
|
|
13
|
-
* of the native `SellwildSDK.apply()` (iOS/Android)
|
|
14
|
-
* resolution.
|
|
13
|
+
* of the native `SellwildSDK.apply()` (iOS/Android) resolution.
|
|
15
14
|
*
|
|
16
15
|
* Both native bridges (feed + banner) consume `config.appBundleId` /
|
|
17
16
|
* `config.appStoreUrl`, so resolving once here makes both correct without any
|
|
18
17
|
* Kotlin/Swift bridge change.
|
|
19
18
|
*
|
|
20
|
-
* Precedence (matches iOS/Android `apply()`
|
|
19
|
+
* Precedence (matches iOS/Android `apply()`): the per-platform CDN
|
|
21
20
|
* value, else the shared `appBundleId`/`appStoreUrl` (which already carries any
|
|
22
21
|
* host override). Backward compatible: no suffixed key = today's behavior.
|
|
23
22
|
*/
|
package/src/commands.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { NativeModules } from 'react-native'
|
|
2
|
-
import
|
|
2
|
+
import { configure as coreConfigure } from '@sellwild/sdk-core'
|
|
3
|
+
import type { SellwildConfig, SellwildEid, SellwildGeo } from '@sellwild/sdk-core'
|
|
4
|
+
import { toNativeConfig } from './nativeConfig'
|
|
3
5
|
|
|
4
6
|
// Imperative bridge to the native Sellwild method module. The ad surface is
|
|
5
7
|
// otherwise view-manager-only (config flows as a prop on <SellwildBanner> /
|
|
@@ -8,6 +10,7 @@ const SellwildRNModule = NativeModules.SellwildRNModule as
|
|
|
8
10
|
| {
|
|
9
11
|
setGeo?: (geo: Record<string, unknown>) => void
|
|
10
12
|
setExternalUserIds?: (eids: SellwildEid[]) => void
|
|
13
|
+
prewarm?: (config: Record<string, unknown>) => void
|
|
11
14
|
}
|
|
12
15
|
| undefined
|
|
13
16
|
|
|
@@ -35,3 +38,29 @@ export function setGeo(geo: SellwildGeo | null): void {
|
|
|
35
38
|
export function setExternalUserIds(eids: SellwildEid[]): void {
|
|
36
39
|
SellwildRNModule?.setExternalUserIds?.(eids ?? [])
|
|
37
40
|
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Pre-initialize the native ad stack (Prebid + ad server SDK) before the first
|
|
44
|
+
* `<SellwildBanner>`/`<SellwildFeed>` mounts, so the first impression doesn't
|
|
45
|
+
* incur cold-start init latency and fall back to server-only demand.
|
|
46
|
+
*
|
|
47
|
+
* Since 1.8.0 `configure()` calls this automatically, so it's only needed for
|
|
48
|
+
* configs built another way (e.g. `buildConfig`). Idempotent, and a no-op if the
|
|
49
|
+
* native module isn't linked.
|
|
50
|
+
*/
|
|
51
|
+
export function prewarm(config: SellwildConfig): void {
|
|
52
|
+
SellwildRNModule?.prewarm?.(toNativeConfig(config))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Fetch the partner's CDN config (see core `configure`) and pre-warm the native
|
|
57
|
+
* ad stack with it, so the first ad view doesn't pay SDK init latency. Matches
|
|
58
|
+
* native iOS / Android `configure()`, which pre-warm too.
|
|
59
|
+
*/
|
|
60
|
+
export async function configure(
|
|
61
|
+
...args: Parameters<typeof coreConfigure>
|
|
62
|
+
): Promise<SellwildConfig> {
|
|
63
|
+
const config = await coreConfigure(...args)
|
|
64
|
+
prewarm(config)
|
|
65
|
+
return config
|
|
66
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
export { SellwildWidget } from './SellwildWidget'
|
|
2
|
-
export type { SellwildWidgetProps } from './SellwildWidget'
|
|
3
|
-
|
|
4
1
|
export { SellwildBanner } from './SellwildBanner'
|
|
5
2
|
export type { SellwildBannerProps } from './SellwildBanner'
|
|
6
3
|
|
|
@@ -14,7 +11,7 @@ export { useSellwildListings } from './useSellwildListings'
|
|
|
14
11
|
export type { UseSellwildListingsResult } from './useSellwildListings'
|
|
15
12
|
|
|
16
13
|
// Imperative native setters (runtime, session-scoped).
|
|
17
|
-
export { setGeo, setExternalUserIds } from './commands'
|
|
14
|
+
export { setGeo, setExternalUserIds, prewarm, configure } from './commands'
|
|
18
15
|
|
|
19
16
|
// Re-export core types for convenience
|
|
20
17
|
export type {
|
|
@@ -34,7 +31,6 @@ export type {
|
|
|
34
31
|
} from '@sellwild/sdk-core'
|
|
35
32
|
|
|
36
33
|
export {
|
|
37
|
-
configure,
|
|
38
34
|
buildConfig,
|
|
39
35
|
buildConfigWithRemote,
|
|
40
36
|
currencyToSymbol,
|
|
@@ -43,3 +39,9 @@ export {
|
|
|
43
39
|
} from '@sellwild/sdk-core'
|
|
44
40
|
|
|
45
41
|
export type { ConfigureOptions } from '@sellwild/sdk-core'
|
|
42
|
+
|
|
43
|
+
// Stamp every analytics event with the host platform. Runs once on module load
|
|
44
|
+
// so any code path that reaches the shared core `eventQueue` is attributed to
|
|
45
|
+
// react-native (merged into `attributes.platform` alongside `sdkVersion`).
|
|
46
|
+
import { eventQueue } from '@sellwild/sdk-core'
|
|
47
|
+
eventQueue.setPlatform('react-native')
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SellwildConfig } from '@sellwild/sdk-core'
|
|
2
|
+
import { resolveAppIdentity } from './appIdentity'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build the native config payload the auction/ad path reads, from a core
|
|
6
|
+
* SellwildConfig. This is the single source of truth for the fields the native
|
|
7
|
+
* banner path + `prewarm` consume — keep `<SellwildBanner>` and `prewarm()` in
|
|
8
|
+
* sync by both going through here.
|
|
9
|
+
*
|
|
10
|
+
* Notes:
|
|
11
|
+
* - App identity is resolved per-platform (iOS vs Android) from `config.remote`.
|
|
12
|
+
* - TS core uses `adRefreshInterval` (millis); the native side reads it as
|
|
13
|
+
* `adRefreshIntervalMs`. The bridge translates.
|
|
14
|
+
* - The raw CDN payload rides on `remote` for the auction passthrough.
|
|
15
|
+
*/
|
|
16
|
+
export function toNativeConfig(config: SellwildConfig): Record<string, unknown> {
|
|
17
|
+
const { appBundleId, appStoreUrl } = resolveAppIdentity(config)
|
|
18
|
+
return {
|
|
19
|
+
partnerCode: config.partnerCode,
|
|
20
|
+
appBundleId,
|
|
21
|
+
appStoreUrl,
|
|
22
|
+
geo: config.geo,
|
|
23
|
+
gamTag: config.gamTag,
|
|
24
|
+
debug: config.debug,
|
|
25
|
+
pbsDebug: config.pbsDebug,
|
|
26
|
+
adRefreshMax: config.adRefreshMax,
|
|
27
|
+
adRefreshMaxMobile: config.adRefreshMaxMobile,
|
|
28
|
+
adRefreshIntervalMs: config.adRefreshInterval,
|
|
29
|
+
prebidServer: config.prebidServer,
|
|
30
|
+
growthCode: config.growthCode,
|
|
31
|
+
localizedListings: config.localizedListings,
|
|
32
|
+
remote: config.remote,
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/SellwildWidget.tsx
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
View,
|
|
4
|
-
StyleSheet,
|
|
5
|
-
ActivityIndicator,
|
|
6
|
-
ViewStyle,
|
|
7
|
-
} from 'react-native'
|
|
8
|
-
import { WebView } from 'react-native-webview'
|
|
9
|
-
import type { WebViewMessageEvent } from 'react-native-webview'
|
|
10
|
-
import type { SellwildConfig, SellwildListing } from '@sellwild/sdk-core'
|
|
11
|
-
import { buildConfig, WIDGET_BASE_URL } from '@sellwild/sdk-core'
|
|
12
|
-
import type { PartialSellwildConfig } from '@sellwild/sdk-core'
|
|
13
|
-
import { buildWidgetHtml } from './htmlBuilder'
|
|
14
|
-
|
|
15
|
-
export interface SellwildWidgetProps {
|
|
16
|
-
config: PartialSellwildConfig
|
|
17
|
-
style?: ViewStyle
|
|
18
|
-
onListingPress?: (listing: SellwildListing) => void
|
|
19
|
-
onAdImpression?: (zoneId: string | number) => void
|
|
20
|
-
onError?: (error: Error) => void
|
|
21
|
-
onLoad?: () => void
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// The web widget intercepts window.open() and sends the listing URL.
|
|
25
|
-
// A full SellwildListing object is not available from the WebView boundary —
|
|
26
|
-
// the listing field will be absent; url contains the Sellwild product page URL.
|
|
27
|
-
type WebViewMessage =
|
|
28
|
-
| { type: 'LISTING_CLICK'; listing?: SellwildListing; url?: string }
|
|
29
|
-
| { type: 'AD_IMPRESSION'; zoneId: string | number }
|
|
30
|
-
| { type: 'WIDGET_LOADED' }
|
|
31
|
-
| { type: 'ERROR'; message: string }
|
|
32
|
-
|
|
33
|
-
export function SellwildWidget({
|
|
34
|
-
config: configProp,
|
|
35
|
-
style,
|
|
36
|
-
onListingPress,
|
|
37
|
-
onAdImpression,
|
|
38
|
-
onError,
|
|
39
|
-
onLoad,
|
|
40
|
-
}: SellwildWidgetProps) {
|
|
41
|
-
// Memoize so that parent re-renders don't change `source.html`, which
|
|
42
|
-
// would force react-native-webview to reload and lose ad session state.
|
|
43
|
-
const config = useMemo(() => buildConfig(configProp), [
|
|
44
|
-
configProp.partnerCode,
|
|
45
|
-
configProp.listingsUrl,
|
|
46
|
-
configProp.gamTag,
|
|
47
|
-
configProp.gptProxyUrl,
|
|
48
|
-
configProp.bannerZid,
|
|
49
|
-
configProp.debug,
|
|
50
|
-
])
|
|
51
|
-
const [loading, setLoading] = useState(true)
|
|
52
|
-
const webViewRef = useRef<WebView>(null)
|
|
53
|
-
|
|
54
|
-
const html = useMemo(() => buildWidgetHtml(config), [config])
|
|
55
|
-
|
|
56
|
-
const handleMessage = useCallback(
|
|
57
|
-
(event: WebViewMessageEvent) => {
|
|
58
|
-
try {
|
|
59
|
-
const msg: WebViewMessage = JSON.parse(event.nativeEvent.data)
|
|
60
|
-
switch (msg.type) {
|
|
61
|
-
case 'LISTING_CLICK':
|
|
62
|
-
// The web widget sends a URL via window.open() interception.
|
|
63
|
-
// A full listing object is not available at the WebView boundary.
|
|
64
|
-
// Pass the listing if somehow present (future compatibility),
|
|
65
|
-
// otherwise construct a minimal stub so callers can navigate.
|
|
66
|
-
if (msg.listing) {
|
|
67
|
-
onListingPress?.(msg.listing)
|
|
68
|
-
} else if (msg.url && onListingPress) {
|
|
69
|
-
const stub: SellwildListing = {
|
|
70
|
-
id: '', status: 'active', title: '',
|
|
71
|
-
text: '', url: msg.url, categoryId: '', categoryGroupId: '',
|
|
72
|
-
currency: 'USD', price: '', strikePrice: '',
|
|
73
|
-
has_photo: false, photo_count: 0, photos: [],
|
|
74
|
-
createdDate: '', videoUrl: '', shippable: '',
|
|
75
|
-
listingType: '', dataSourceId: '',
|
|
76
|
-
user: { id: '', firstName: '', lastName: '', username: '',
|
|
77
|
-
membershipType: '', trustLevel: '', has_photo: '',
|
|
78
|
-
photos: [], isPhoneVerified: false },
|
|
79
|
-
}
|
|
80
|
-
onListingPress(stub)
|
|
81
|
-
}
|
|
82
|
-
break
|
|
83
|
-
case 'AD_IMPRESSION':
|
|
84
|
-
onAdImpression?.(msg.zoneId)
|
|
85
|
-
break
|
|
86
|
-
case 'WIDGET_LOADED':
|
|
87
|
-
setLoading(false)
|
|
88
|
-
onLoad?.()
|
|
89
|
-
break
|
|
90
|
-
case 'ERROR':
|
|
91
|
-
onError?.(new Error(msg.message))
|
|
92
|
-
break
|
|
93
|
-
}
|
|
94
|
-
} catch {
|
|
95
|
-
// Non-SDK message, ignore
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
[onListingPress, onAdImpression, onError, onLoad]
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<View style={[styles.container, style]}>
|
|
103
|
-
{loading && (
|
|
104
|
-
<View style={styles.loader}>
|
|
105
|
-
<ActivityIndicator size="large" />
|
|
106
|
-
</View>
|
|
107
|
-
)}
|
|
108
|
-
<WebView
|
|
109
|
-
ref={webViewRef}
|
|
110
|
-
source={{ html, baseUrl: 'https://widget.sellwild.com/' }}
|
|
111
|
-
style={styles.webView}
|
|
112
|
-
onMessage={handleMessage}
|
|
113
|
-
javaScriptEnabled
|
|
114
|
-
domStorageEnabled
|
|
115
|
-
thirdPartyCookiesEnabled
|
|
116
|
-
allowsInlineMediaPlayback
|
|
117
|
-
mediaPlaybackRequiresUserAction={false}
|
|
118
|
-
mixedContentMode="compatibility"
|
|
119
|
-
originWhitelist={['*']}
|
|
120
|
-
onError={syntheticEvent => {
|
|
121
|
-
const { nativeEvent } = syntheticEvent
|
|
122
|
-
onError?.(new Error(nativeEvent.description))
|
|
123
|
-
}}
|
|
124
|
-
/>
|
|
125
|
-
</View>
|
|
126
|
-
)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const styles = StyleSheet.create({
|
|
130
|
-
container: {
|
|
131
|
-
flex: 1,
|
|
132
|
-
minHeight: 300,
|
|
133
|
-
},
|
|
134
|
-
webView: {
|
|
135
|
-
flex: 1,
|
|
136
|
-
backgroundColor: 'transparent',
|
|
137
|
-
},
|
|
138
|
-
loader: {
|
|
139
|
-
...StyleSheet.absoluteFillObject,
|
|
140
|
-
justifyContent: 'center',
|
|
141
|
-
alignItems: 'center',
|
|
142
|
-
backgroundColor: '#f5f5f5',
|
|
143
|
-
zIndex: 1,
|
|
144
|
-
},
|
|
145
|
-
})
|