@sellwild/react-native-sdk 1.7.1 → 1.7.3
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/src/main/java/com/sellwild/rnsdk/SellwildFeedViewManager.kt +6 -0
- package/ios/SellwildFeedViewManager.m +1 -0
- package/ios/SellwildFeedViewManager.swift +7 -0
- package/package.json +1 -1
- package/src/SellwildBanner.tsx +6 -2
- package/src/SellwildFeed.tsx +19 -2
- package/src/appIdentity.ts +40 -0
|
@@ -83,6 +83,11 @@ class SellwildFeedViewManager : SimpleViewManager<SellwildFeedView>() {
|
|
|
83
83
|
emit(reactContext, view, "onFeedLoaded", null)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
override fun onFeedReady(listingCount: Int) {
|
|
87
|
+
val payload = Arguments.createMap().apply { putInt("listingCount", listingCount) }
|
|
88
|
+
emit(reactContext, view, "onFeedReady", payload)
|
|
89
|
+
}
|
|
90
|
+
|
|
86
91
|
override fun onError(message: String) {
|
|
87
92
|
val payload = Arguments.createMap().apply { putString("message", message) }
|
|
88
93
|
emit(reactContext, view, "onFeedError", payload)
|
|
@@ -132,6 +137,7 @@ class SellwildFeedViewManager : SimpleViewManager<SellwildFeedView>() {
|
|
|
132
137
|
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
|
|
133
138
|
return MapBuilder.builder<String, Any>()
|
|
134
139
|
.put("onFeedLoaded", MapBuilder.of("registrationName", "onFeedLoaded"))
|
|
140
|
+
.put("onFeedReady", MapBuilder.of("registrationName", "onFeedReady"))
|
|
135
141
|
.put("onListingTap", MapBuilder.of("registrationName", "onListingTap"))
|
|
136
142
|
.put("onAdImpression", MapBuilder.of("registrationName", "onAdImpression"))
|
|
137
143
|
.put("onHouseAdImpression", MapBuilder.of("registrationName", "onHouseAdImpression"))
|
|
@@ -16,6 +16,7 @@ RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
|
|
|
16
16
|
|
|
17
17
|
// Direct events emitted to JS callbacks.
|
|
18
18
|
RCT_EXPORT_VIEW_PROPERTY(onFeedLoaded, RCTDirectEventBlock)
|
|
19
|
+
RCT_EXPORT_VIEW_PROPERTY(onFeedReady, RCTDirectEventBlock)
|
|
19
20
|
RCT_EXPORT_VIEW_PROPERTY(onListingTap, RCTDirectEventBlock)
|
|
20
21
|
RCT_EXPORT_VIEW_PROPERTY(onAdImpression, RCTDirectEventBlock)
|
|
21
22
|
RCT_EXPORT_VIEW_PROPERTY(onHouseAdImpression, RCTDirectEventBlock)
|
|
@@ -45,6 +45,9 @@ final class SellwildFeedHostView: UIView, SellwildFeedViewDelegate {
|
|
|
45
45
|
// MARK: RN events
|
|
46
46
|
|
|
47
47
|
@objc var onFeedLoaded: RCTDirectEventBlock?
|
|
48
|
+
/// Fires after a successful fetch with the bound listing count
|
|
49
|
+
/// (`listingCount == 0` ⇒ empty / header-only). Reliable "ready" signal.
|
|
50
|
+
@objc var onFeedReady: RCTDirectEventBlock?
|
|
48
51
|
@objc var onListingTap: RCTDirectEventBlock?
|
|
49
52
|
@objc var onAdImpression: RCTDirectEventBlock?
|
|
50
53
|
/// A house ad backfilled an empty feed slot (no-fill). Not a paid impression.
|
|
@@ -110,6 +113,10 @@ final class SellwildFeedHostView: UIView, SellwildFeedViewDelegate {
|
|
|
110
113
|
onFeedLoaded?([:])
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
func sellwildFeed(_ feed: SellwildFeedView, didBecomeReadyWithListingCount count: Int) {
|
|
117
|
+
onFeedReady?(["listingCount": count])
|
|
118
|
+
}
|
|
119
|
+
|
|
113
120
|
func sellwildFeed(_ feed: SellwildFeedView, didTapListing listing: SellwildListing) -> Bool {
|
|
114
121
|
// Forward the listing payload to JS as a plain dictionary. JS
|
|
115
122
|
// can't return a value back through a direct event block, so
|
package/package.json
CHANGED
package/src/SellwildBanner.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Platform, requireNativeComponent, UIManager, ViewStyle, View, Text, StyleSheet, NativeSyntheticEvent } from 'react-native'
|
|
3
3
|
import { resolveAdStack, type SellwildConfig, type AdSize } from '@sellwild/sdk-core'
|
|
4
|
+
import { resolveAppIdentity } from './appIdentity'
|
|
4
5
|
|
|
5
6
|
// Standard IAB mobile ad sizes — used to lock the host View dimensions.
|
|
6
7
|
// The actual ad size is also propagated to native as the `size` prop label.
|
|
@@ -162,10 +163,13 @@ export function SellwildBanner({
|
|
|
162
163
|
// of SellwildConfig is ignored on the native side. Note: TS core uses
|
|
163
164
|
// `adRefreshInterval` (millis); the native side reads it as
|
|
164
165
|
// `adRefreshIntervalMs`. The bridge translates.
|
|
166
|
+
// App identity is resolved per-platform here (iOS vs Android) from the raw
|
|
167
|
+
// CDN payload on `config.remote`; the native banner path reads these fields.
|
|
168
|
+
const { appBundleId, appStoreUrl } = resolveAppIdentity(config)
|
|
165
169
|
const nativeConfig = {
|
|
166
170
|
partnerCode: config.partnerCode,
|
|
167
|
-
appBundleId
|
|
168
|
-
appStoreUrl
|
|
171
|
+
appBundleId,
|
|
172
|
+
appStoreUrl,
|
|
169
173
|
geo: config.geo,
|
|
170
174
|
gamTag: config.gamTag,
|
|
171
175
|
debug: config.debug,
|
package/src/SellwildFeed.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
NativeSyntheticEvent,
|
|
12
12
|
} from 'react-native'
|
|
13
13
|
import type { SellwildConfig, SellwildListing } from '@sellwild/sdk-core'
|
|
14
|
+
import { resolveAppIdentity } from './appIdentity'
|
|
14
15
|
|
|
15
16
|
// ─── Native component bridge ─────────────────────────────────────────────────
|
|
16
17
|
//
|
|
@@ -32,6 +33,7 @@ interface NativeFeedProps {
|
|
|
32
33
|
scrollEnabled?: boolean
|
|
33
34
|
style?: StyleProp<ViewStyle>
|
|
34
35
|
onFeedLoaded?: (e: NativeSyntheticEvent<{}>) => void
|
|
36
|
+
onFeedReady?: (e: NativeSyntheticEvent<{ listingCount: number }>) => void
|
|
35
37
|
onListingTap?: (e: NativeSyntheticEvent<{ listing: SellwildListing }>) => void
|
|
36
38
|
onAdImpression?: (e: NativeSyntheticEvent<{ zoneId: string }>) => void
|
|
37
39
|
onHouseAdImpression?: (e: NativeSyntheticEvent<{ zoneId: string }>) => void
|
|
@@ -84,6 +86,14 @@ export interface SellwildFeedProps {
|
|
|
84
86
|
/** Fired once after the initial listings fetch resolves successfully. */
|
|
85
87
|
onLoad?: () => void
|
|
86
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Fired after a successful fetch with the number of listings bound to the
|
|
91
|
+
* feed. `listingCount === 0` means an empty / header-only render. Prefer this
|
|
92
|
+
* over `onLoad` when you need to know the feed is actually populated — `onLoad`
|
|
93
|
+
* also fires on an empty result.
|
|
94
|
+
*/
|
|
95
|
+
onFeedReady?: (listingCount: number) => void
|
|
96
|
+
|
|
87
97
|
/**
|
|
88
98
|
* Fired when a listing card is tapped. Return `true` to consume the
|
|
89
99
|
* event; return `false` (or omit) to let the SDK open `listing.url`
|
|
@@ -113,6 +123,7 @@ export function SellwildFeed({
|
|
|
113
123
|
scrollEnabled = true,
|
|
114
124
|
onContentSizeChange,
|
|
115
125
|
onLoad,
|
|
126
|
+
onFeedReady,
|
|
116
127
|
onListingTap,
|
|
117
128
|
onAdImpression,
|
|
118
129
|
onHouseAdImpression,
|
|
@@ -151,11 +162,14 @@ export function SellwildFeed({
|
|
|
151
162
|
//
|
|
152
163
|
// Note: TS core uses `adRefreshInterval` (ms); the native side
|
|
153
164
|
// reads it as `adRefreshIntervalMs`. The bridge translates.
|
|
165
|
+
// App identity is resolved per-platform here (iOS vs Android) from the raw
|
|
166
|
+
// CDN payload on `config.remote`; both native bridges read these fields.
|
|
167
|
+
const { appBundleId, appStoreUrl } = resolveAppIdentity(config)
|
|
154
168
|
const nativeConfig: Record<string, unknown> = {
|
|
155
169
|
partnerCode: config.partnerCode,
|
|
156
170
|
slug: config.slug,
|
|
157
|
-
appBundleId
|
|
158
|
-
appStoreUrl
|
|
171
|
+
appBundleId,
|
|
172
|
+
appStoreUrl,
|
|
159
173
|
geo: config.geo,
|
|
160
174
|
gamTag: config.gamTag,
|
|
161
175
|
debug: config.debug,
|
|
@@ -191,6 +205,9 @@ export function SellwildFeed({
|
|
|
191
205
|
onContentSizeChange?.({ width, height })
|
|
192
206
|
}}
|
|
193
207
|
onFeedLoaded={() => onLoad?.()}
|
|
208
|
+
onFeedReady={(e: NativeSyntheticEvent<{ listingCount: number }>) => {
|
|
209
|
+
onFeedReady?.(e.nativeEvent.listingCount)
|
|
210
|
+
}}
|
|
194
211
|
onListingTap={(e: NativeSyntheticEvent<{ listing: SellwildListing }>) => {
|
|
195
212
|
onListingTap?.(e.nativeEvent.listing)
|
|
196
213
|
}}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Platform } from 'react-native'
|
|
2
|
+
import type { SellwildConfig } from '@sellwild/sdk-core'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve per-platform app identity for the native bridge.
|
|
6
|
+
*
|
|
7
|
+
* The CMS may carry OS-specific overrides (`APP_BUNDLE_ID_IOS`/`_ANDROID`,
|
|
8
|
+
* `APP_STORE_URL_IOS`/`_ANDROID`) alongside the shared `APP_BUNDLE_ID` /
|
|
9
|
+
* `APP_STORE_URL`. `core`'s `mapRemoteConfig` maps only the shared
|
|
10
|
+
* (OS-agnostic) keys onto `appBundleId`/`appStoreUrl` and stashes the raw
|
|
11
|
+
* payload on `config.remote`; the platform is only known here on the device,
|
|
12
|
+
* so we resolve the OS-specific value at the bridge boundary — the RN analog
|
|
13
|
+
* of the native `SellwildSDK.apply()` (iOS/Android) and Flutter `dart:io`
|
|
14
|
+
* resolution.
|
|
15
|
+
*
|
|
16
|
+
* Both native bridges (feed + banner) consume `config.appBundleId` /
|
|
17
|
+
* `config.appStoreUrl`, so resolving once here makes both correct without any
|
|
18
|
+
* Kotlin/Swift bridge change.
|
|
19
|
+
*
|
|
20
|
+
* Precedence (matches iOS/Android `apply()` and Flutter): the per-platform CDN
|
|
21
|
+
* value, else the shared `appBundleId`/`appStoreUrl` (which already carries any
|
|
22
|
+
* host override). Backward compatible: no suffixed key = today's behavior.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveAppIdentity(config: SellwildConfig): {
|
|
25
|
+
appBundleId?: string
|
|
26
|
+
appStoreUrl?: string
|
|
27
|
+
} {
|
|
28
|
+
const remote = (config.remote ?? {}) as Record<string, unknown>
|
|
29
|
+
const isAndroid = Platform.OS === 'android'
|
|
30
|
+
const pick = (androidKey: string, iosKey: string): string | undefined => {
|
|
31
|
+
const v = remote[isAndroid ? androidKey : iosKey]
|
|
32
|
+
return typeof v === 'string' && v !== '' ? v : undefined
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
appBundleId:
|
|
36
|
+
pick('APP_BUNDLE_ID_ANDROID', 'APP_BUNDLE_ID_IOS') ?? config.appBundleId,
|
|
37
|
+
appStoreUrl:
|
|
38
|
+
pick('APP_STORE_URL_ANDROID', 'APP_STORE_URL_IOS') ?? config.appStoreUrl,
|
|
39
|
+
}
|
|
40
|
+
}
|