@namiml/expo-sdk 3.4.1 → 3.4.2-dev.202605300214
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/dist/index.cjs +40 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +42 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
- package/src/components/NamiView.tsx +20 -6
- package/src/components/PaywallScreen.tsx +14 -0
- package/src/components/containers/NamiContentContainer.tsx +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@namiml/expo-sdk",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.2-dev.202605300214",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Nami Expo SDK — paywall and subscription management for Expo apps",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -29,9 +29,10 @@
|
|
|
29
29
|
"prepublishOnly": "yarn clean && yarn build:prod"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@namiml/expo-nami-iap": "3.4.
|
|
33
|
-
"@namiml/sdk-core": "3.4.
|
|
32
|
+
"@namiml/expo-nami-iap": "3.4.2-dev.202605300214",
|
|
33
|
+
"@namiml/sdk-core": "3.4.2-dev.202605300214",
|
|
34
34
|
"react-native-qrcode-svg": "^6.3.21",
|
|
35
|
+
"react-native-safe-area-context": "^5.6.0",
|
|
35
36
|
"react-native-svg": "^15.15.4"
|
|
36
37
|
},
|
|
37
38
|
"peerDependencies": {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect, useLayoutEffect, useRef, useMemo, useCallback } from 'react';
|
|
2
|
-
import { View, StyleSheet, Animated, Dimensions,
|
|
2
|
+
import { View, StyleSheet, Animated, Dimensions, StatusBar, BackHandler, ActivityIndicator } from 'react-native';
|
|
3
|
+
import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context';
|
|
3
4
|
import type {
|
|
4
5
|
IPaywall,
|
|
5
6
|
NamiPaywallLaunchContext,
|
|
@@ -11,6 +12,7 @@ import type {
|
|
|
11
12
|
import {
|
|
12
13
|
getPaywallDataFromLabel,
|
|
13
14
|
getPaywall,
|
|
15
|
+
isValidUrl,
|
|
14
16
|
isNamiFlowCampaign,
|
|
15
17
|
NamiReservedActions,
|
|
16
18
|
hasAllPaywalls,
|
|
@@ -141,7 +143,13 @@ export const NamiView: React.FC<NamiViewProps> = ({
|
|
|
141
143
|
const resolvedLaunch = useMemo<LaunchRequest>(() => {
|
|
142
144
|
if (launchRequest?.value) return launchRequest;
|
|
143
145
|
if (url) return { type: 'url', value: url };
|
|
144
|
-
if (placement)
|
|
146
|
+
if (placement) {
|
|
147
|
+
// A URL/deeplink campaign carries its URL in the value. If one is handed
|
|
148
|
+
// in via the placement prop, classify it as a url launch so core takes
|
|
149
|
+
// the URL-match branch — otherwise the label lookup misses, no paywall
|
|
150
|
+
// resolves, and NamiView spins on the placeholder forever.
|
|
151
|
+
return { type: isValidUrl(placement) ? 'url' : 'label', value: placement };
|
|
152
|
+
}
|
|
145
153
|
return { type: undefined, value: '' };
|
|
146
154
|
}, [launchRequest, url, placement]);
|
|
147
155
|
|
|
@@ -638,7 +646,7 @@ export const NamiView: React.FC<NamiViewProps> = ({
|
|
|
638
646
|
}, []);
|
|
639
647
|
|
|
640
648
|
if (isClosing) {
|
|
641
|
-
return <
|
|
649
|
+
return <View style={styles.root} />;
|
|
642
650
|
}
|
|
643
651
|
|
|
644
652
|
if (loading || !campaignData || (!isFlowCampaign && !paywallData) || (isFlowCampaign && !hasFlowScreen)) {
|
|
@@ -646,8 +654,14 @@ export const NamiView: React.FC<NamiViewProps> = ({
|
|
|
646
654
|
}
|
|
647
655
|
|
|
648
656
|
return (
|
|
649
|
-
|
|
650
|
-
|
|
657
|
+
// Full-bleed root so the paywall background extends under the status bar and
|
|
658
|
+
// home indicator (edge-to-edge). Safe-area insets are applied only to the
|
|
659
|
+
// chrome (header/footer) via useSafeAreaInsets, not the whole tree — the
|
|
660
|
+
// previous core SafeAreaView inset every edge and let the #000 root show
|
|
661
|
+
// through as letterbox bands. SafeAreaProvider guarantees an inset context
|
|
662
|
+
// even if the host app didn't mount one (nested providers are supported).
|
|
663
|
+
<SafeAreaProvider initialMetrics={initialWindowMetrics} style={styles.root}>
|
|
664
|
+
<StatusBar barStyle="light-content" translucent backgroundColor="transparent" />
|
|
651
665
|
{isFlowCampaign ? (
|
|
652
666
|
<FlowRenderer
|
|
653
667
|
paywalls={flowState.paywalls}
|
|
@@ -673,7 +687,7 @@ export const NamiView: React.FC<NamiViewProps> = ({
|
|
|
673
687
|
)}
|
|
674
688
|
{!isClosing && showLaunchPlaceholder && <LaunchPlaceholder paywall={launchPlaceholderPaywall} overlay />}
|
|
675
689
|
{showTransitionPlaceholder && <LaunchPlaceholder paywall={pendingTransitionPaywall} overlay />}
|
|
676
|
-
</
|
|
690
|
+
</SafeAreaProvider>
|
|
677
691
|
);
|
|
678
692
|
};
|
|
679
693
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { ActivityIndicator, BackHandler, View, StyleSheet } from 'react-native';
|
|
3
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
3
4
|
import type { IPaywall, TPages } from '@namiml/sdk-core';
|
|
4
5
|
import { useFirstFocusReadyContext, usePaywallContext } from '../context/PaywallContext';
|
|
5
6
|
import { NamiContentContainer } from './containers/NamiContentContainer';
|
|
@@ -36,7 +37,20 @@ export const PaywallScreen: React.FC<Props> = ({
|
|
|
36
37
|
isActive = true,
|
|
37
38
|
}) => {
|
|
38
39
|
const ctx = usePaywallContext();
|
|
40
|
+
const insets = useSafeAreaInsets();
|
|
39
41
|
const focusReadyCtx = useFirstFocusReadyContext();
|
|
42
|
+
|
|
43
|
+
// Push the measured top safe-area inset into paywall state so templates can
|
|
44
|
+
// position chrome via ${state.safeAreaTop} (e.g. header topPadding,
|
|
45
|
+
// background topMargin). Mirrors the native Android renderer, which measures
|
|
46
|
+
// the display cutout and calls setSafeAreaTop. The full-bleed root lets the
|
|
47
|
+
// background extend under the status bar; this offsets the content back into
|
|
48
|
+
// the safe area. No safeAreaBottom equivalent exists in core/Android state.
|
|
49
|
+
// Depend only on insets.top (stable per device/orientation) — depending on
|
|
50
|
+
// ctx would re-run every render and loop through setState.
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
ctx.setSafeAreaTop(insets.top);
|
|
53
|
+
}, [insets.top]);
|
|
40
54
|
const scaleFactor = getDeviceScaleFactor(ctx.state.formFactor);
|
|
41
55
|
const userInteractionEnabled = ctx.state.userInteractionEnabled !== false;
|
|
42
56
|
const currentPageName = ctx.state.selectedPaywall === paywall
|
|
@@ -19,6 +19,17 @@ function splitContainerStyles(style: ViewStyle): { outer: ViewStyle; inner: View
|
|
|
19
19
|
} = style;
|
|
20
20
|
|
|
21
21
|
const outer: ViewStyle = { ...rest };
|
|
22
|
+
// The content container is always the page's vertical fill region: styles.outer
|
|
23
|
+
// declares flex:1 and the body scrolls internally. A payload height of
|
|
24
|
+
// 'fitContent' makes sizeStyles emit flexGrow:0, which would otherwise override
|
|
25
|
+
// that fill and collapse the body to 0 height — the flex:1 ScrollView child
|
|
26
|
+
// can't give an unbounded parent a height. Drop the flex-sizing keys so the
|
|
27
|
+
// fill stays authoritative, matching the native renderers which always
|
|
28
|
+
// fill-and-scroll this region regardless of the authored height.
|
|
29
|
+
delete outer.flex;
|
|
30
|
+
delete outer.flexGrow;
|
|
31
|
+
delete outer.flexBasis;
|
|
32
|
+
delete outer.flexShrink;
|
|
22
33
|
const inner: ViewStyle = {
|
|
23
34
|
...(paddingLeft != null ? { paddingLeft } : {}),
|
|
24
35
|
...(paddingRight != null ? { paddingRight } : {}),
|