@umituz/react-native-subscription 3.1.25 → 3.1.27
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.27",
|
|
4
4
|
"description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -12,6 +12,8 @@ interface PaywallFooterProps {
|
|
|
12
12
|
onRestore?: () => Promise<void | boolean>;
|
|
13
13
|
onLegalClick: (url: string | undefined) => void;
|
|
14
14
|
purchaseButtonText?: string;
|
|
15
|
+
isTablet?: boolean;
|
|
16
|
+
isSmallDevice?: boolean;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
|
|
@@ -22,12 +24,33 @@ export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
|
|
|
22
24
|
onRestore,
|
|
23
25
|
onLegalClick,
|
|
24
26
|
purchaseButtonText,
|
|
27
|
+
isTablet = false,
|
|
28
|
+
isSmallDevice = false,
|
|
25
29
|
}) => {
|
|
26
30
|
const tokens = useAppDesignTokens();
|
|
27
31
|
|
|
32
|
+
// Device-based sizing
|
|
33
|
+
const buttonHeight = React.useMemo(() => {
|
|
34
|
+
if (isTablet) return 64; // Larger button on tablet
|
|
35
|
+
if (isSmallDevice) return 52; // Slightly smaller on small phones
|
|
36
|
+
return 56; // Standard size
|
|
37
|
+
}, [isTablet, isSmallDevice]);
|
|
38
|
+
|
|
39
|
+
const fontSize = React.useMemo(() => {
|
|
40
|
+
if (isTablet) return 19;
|
|
41
|
+
if (isSmallDevice) return 16;
|
|
42
|
+
return 17;
|
|
43
|
+
}, [isTablet, isSmallDevice]);
|
|
44
|
+
|
|
45
|
+
const paddingVertical = React.useMemo(() => {
|
|
46
|
+
if (isTablet) return 20;
|
|
47
|
+
if (isSmallDevice) return 12;
|
|
48
|
+
return 16;
|
|
49
|
+
}, [isTablet, isSmallDevice]);
|
|
50
|
+
|
|
28
51
|
return (
|
|
29
52
|
<View style={footerStyles.container}>
|
|
30
|
-
{/* Purchase Button -
|
|
53
|
+
{/* Purchase Button - Device-responsive */}
|
|
31
54
|
{onPurchase && (
|
|
32
55
|
<TouchableOpacity
|
|
33
56
|
onPress={onPurchase}
|
|
@@ -35,7 +58,11 @@ export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
|
|
|
35
58
|
activeOpacity={0.8}
|
|
36
59
|
style={[
|
|
37
60
|
footerStyles.purchaseButton,
|
|
38
|
-
{
|
|
61
|
+
{
|
|
62
|
+
backgroundColor: tokens.colors.primary,
|
|
63
|
+
height: buttonHeight,
|
|
64
|
+
paddingVertical: paddingVertical,
|
|
65
|
+
},
|
|
39
66
|
isProcessing && footerStyles.buttonDisabled,
|
|
40
67
|
]}
|
|
41
68
|
>
|
|
@@ -44,20 +71,20 @@ export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
|
|
|
44
71
|
<AtomicSpinner size="sm" />
|
|
45
72
|
</View>
|
|
46
73
|
) : (
|
|
47
|
-
<AtomicText style={[footerStyles.purchaseButtonText, { color: tokens.colors.textPrimary }]}>
|
|
74
|
+
<AtomicText style={[footerStyles.purchaseButtonText, { color: tokens.colors.textPrimary, fontSize }]}>
|
|
48
75
|
{purchaseButtonText || translations.purchaseButtonText}
|
|
49
76
|
</AtomicText>
|
|
50
77
|
)}
|
|
51
78
|
</TouchableOpacity>
|
|
52
79
|
)}
|
|
53
80
|
|
|
54
|
-
{/* Restore Link -
|
|
81
|
+
{/* Restore Link - Device-spaced */}
|
|
55
82
|
{onRestore && (
|
|
56
83
|
<TouchableOpacity
|
|
57
84
|
onPress={onRestore}
|
|
58
85
|
disabled={isProcessing}
|
|
59
86
|
activeOpacity={0.6}
|
|
60
|
-
style={footerStyles.restoreButton}
|
|
87
|
+
style={[footerStyles.restoreButton, { marginTop: isTablet ? 16 : 8 }]}
|
|
61
88
|
>
|
|
62
89
|
<AtomicText style={[footerStyles.restoreText, { color: tokens.colors.textSecondary }]}>
|
|
63
90
|
{translations.restoreButtonText}
|
|
@@ -65,7 +92,7 @@ export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
|
|
|
65
92
|
</TouchableOpacity>
|
|
66
93
|
)}
|
|
67
94
|
|
|
68
|
-
{/* Legal Links -
|
|
95
|
+
{/* Legal Links - Responsive spacing */}
|
|
69
96
|
<View style={footerStyles.legalContainer}>
|
|
70
97
|
{legalUrls.termsUrl && (
|
|
71
98
|
<TouchableOpacity
|
|
@@ -105,7 +132,6 @@ const footerStyles = StyleSheet.create({
|
|
|
105
132
|
},
|
|
106
133
|
purchaseButton: {
|
|
107
134
|
width: '100%',
|
|
108
|
-
height: 56,
|
|
109
135
|
borderRadius: 16,
|
|
110
136
|
justifyContent: 'center',
|
|
111
137
|
alignItems: 'center',
|
|
@@ -123,16 +149,13 @@ const footerStyles = StyleSheet.create({
|
|
|
123
149
|
alignItems: 'center',
|
|
124
150
|
},
|
|
125
151
|
purchaseButtonText: {
|
|
126
|
-
fontSize: 17,
|
|
127
152
|
fontWeight: '700',
|
|
128
153
|
letterSpacing: 0.5,
|
|
129
154
|
},
|
|
130
155
|
restoreButton: {
|
|
131
156
|
alignItems: 'center',
|
|
132
|
-
paddingVertical: 4,
|
|
133
157
|
},
|
|
134
158
|
restoreText: {
|
|
135
|
-
fontSize: 13,
|
|
136
159
|
fontWeight: '500',
|
|
137
160
|
textDecorationLine: 'underline',
|
|
138
161
|
},
|
|
@@ -143,11 +166,9 @@ const footerStyles = StyleSheet.create({
|
|
|
143
166
|
flexWrap: 'wrap',
|
|
144
167
|
},
|
|
145
168
|
legalText: {
|
|
146
|
-
fontSize: 11,
|
|
147
169
|
lineHeight: 16,
|
|
148
170
|
},
|
|
149
171
|
legalSeparator: {
|
|
150
|
-
fontSize: 11,
|
|
151
172
|
color: 'rgba(255, 255, 255, 0.4)',
|
|
152
173
|
marginHorizontal: 4,
|
|
153
174
|
},
|
|
@@ -7,15 +7,16 @@ import React from "react";
|
|
|
7
7
|
import { View } from "react-native";
|
|
8
8
|
import type { ImageSourcePropType } from "react-native";
|
|
9
9
|
import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
|
|
10
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
10
11
|
import { Image } from "expo-image";
|
|
11
12
|
import { PlanCard } from "./PlanCard";
|
|
12
13
|
import type { PaywallListItem } from "../utils/paywallLayoutUtils";
|
|
14
|
+
import type { PaywallTranslations } from "../entities/types";
|
|
13
15
|
import { paywallScreenStyles as styles } from "./PaywallScreen.styles";
|
|
14
16
|
|
|
15
17
|
interface PaywallRenderItemProps {
|
|
16
18
|
item: PaywallListItem;
|
|
17
|
-
|
|
18
|
-
translations: any;
|
|
19
|
+
translations: PaywallTranslations;
|
|
19
20
|
heroImage?: ImageSourcePropType;
|
|
20
21
|
selectedPlanId?: string;
|
|
21
22
|
bestValueIdentifier?: string;
|
|
@@ -26,7 +27,6 @@ interface PaywallRenderItemProps {
|
|
|
26
27
|
|
|
27
28
|
export const PaywallRenderItem: React.FC<PaywallRenderItemProps> = React.memo(({
|
|
28
29
|
item,
|
|
29
|
-
tokens,
|
|
30
30
|
translations,
|
|
31
31
|
heroImage,
|
|
32
32
|
selectedPlanId,
|
|
@@ -35,6 +35,8 @@ export const PaywallRenderItem: React.FC<PaywallRenderItemProps> = React.memo(({
|
|
|
35
35
|
creditsLabel,
|
|
36
36
|
onSelectPlan,
|
|
37
37
|
}) => {
|
|
38
|
+
const tokens = useAppDesignTokens();
|
|
39
|
+
|
|
38
40
|
if (!translations) return null;
|
|
39
41
|
|
|
40
42
|
switch (item.type) {
|
|
@@ -73,7 +75,7 @@ export const PaywallRenderItem: React.FC<PaywallRenderItemProps> = React.memo(({
|
|
|
73
75
|
|
|
74
76
|
case 'FEATURE':
|
|
75
77
|
return (
|
|
76
|
-
<View key={`feat-${item.feature.text}`} style={
|
|
78
|
+
<View key={`feat-${item.feature.text}`} style={styles.featureRow}>
|
|
77
79
|
<View style={[styles.featureIcon, { backgroundColor: tokens.colors.primary }]}>
|
|
78
80
|
<AtomicIcon name={item.feature.icon} customSize={16} customColor={tokens.colors.onPrimary} />
|
|
79
81
|
</View>
|
|
@@ -18,6 +18,7 @@ import { useNavigation } from "@react-navigation/native";
|
|
|
18
18
|
import { AtomicIcon, AtomicSpinner } from "@umituz/react-native-design-system/atoms";
|
|
19
19
|
import { useSafeAreaInsets } from "@umituz/react-native-design-system/safe-area";
|
|
20
20
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
21
|
+
import { useResponsive } from "@umituz/react-native-design-system/responsive";
|
|
21
22
|
import { paywallScreenStyles as styles } from "./PaywallScreen.styles";
|
|
22
23
|
import { PaywallFooter } from "./PaywallFooter";
|
|
23
24
|
import { usePaywallActions } from "../hooks/usePaywallActions";
|
|
@@ -29,6 +30,10 @@ import {
|
|
|
29
30
|
import { hasItems } from "../../../shared/utils/arrayUtils";
|
|
30
31
|
import { PaywallRenderItem } from "./PaywallScreen.renderItem";
|
|
31
32
|
|
|
33
|
+
// Paywall layout constants
|
|
34
|
+
const PAYWALL_HEADER_HEIGHT = 60; // Close button + spacing
|
|
35
|
+
const PAYWALL_FOOTER_HEIGHT_BASE = 280; // Base footer height
|
|
36
|
+
|
|
32
37
|
export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) => {
|
|
33
38
|
const navigation = useNavigation();
|
|
34
39
|
|
|
@@ -61,6 +66,7 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
61
66
|
|
|
62
67
|
const tokens = useAppDesignTokens();
|
|
63
68
|
const insets = useSafeAreaInsets();
|
|
69
|
+
const responsive = useResponsive();
|
|
64
70
|
|
|
65
71
|
const handleClose = useCallback(() => {
|
|
66
72
|
if (__DEV__) console.log('[PaywallScreen] 🔙 Closing paywall');
|
|
@@ -146,7 +152,6 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
146
152
|
return (
|
|
147
153
|
<PaywallRenderItem
|
|
148
154
|
item={item}
|
|
149
|
-
tokens={tokens}
|
|
150
155
|
translations={translations}
|
|
151
156
|
heroImage={heroImage}
|
|
152
157
|
selectedPlanId={selectedPlanId ?? undefined}
|
|
@@ -156,7 +161,7 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
156
161
|
onSelectPlan={setSelectedPlanId}
|
|
157
162
|
/>
|
|
158
163
|
);
|
|
159
|
-
}, [
|
|
164
|
+
}, [translations, heroImage, selectedPlanId, bestValueIdentifier, creditAmounts, creditsLabel, setSelectedPlanId]);
|
|
160
165
|
|
|
161
166
|
// Performance Optimization: getItemLayout for FlatList
|
|
162
167
|
const getItemLayout = useCallback((_data: ArrayLike<PaywallListItem> | null | undefined, index: number) => {
|
|
@@ -190,11 +195,11 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
190
195
|
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
191
196
|
<StatusBar barStyle="light-content" />
|
|
192
197
|
|
|
193
|
-
{/* Absolute Close Button */}
|
|
198
|
+
{/* Absolute Close Button - Responsive positioning */}
|
|
194
199
|
<View style={{
|
|
195
200
|
position: 'absolute',
|
|
196
201
|
top: Math.max(insets.top, 16),
|
|
197
|
-
right:
|
|
202
|
+
right: 16,
|
|
198
203
|
zIndex: 10,
|
|
199
204
|
}}>
|
|
200
205
|
<TouchableOpacity
|
|
@@ -206,7 +211,7 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
206
211
|
</TouchableOpacity>
|
|
207
212
|
</View>
|
|
208
213
|
|
|
209
|
-
{/* Main Content */}
|
|
214
|
+
{/* Main Content - Responsive spacing */}
|
|
210
215
|
<FlatList
|
|
211
216
|
data={flatData}
|
|
212
217
|
renderItem={renderItem}
|
|
@@ -220,15 +225,16 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
220
225
|
contentContainerStyle={[
|
|
221
226
|
styles.listContent,
|
|
222
227
|
{
|
|
223
|
-
paddingTop: Math.max(insets.top, 20) +
|
|
224
|
-
paddingBottom:
|
|
228
|
+
paddingTop: Math.max(insets.top, 20) + PAYWALL_HEADER_HEIGHT, // Responsive header spacing
|
|
229
|
+
paddingBottom: PAYWALL_FOOTER_HEIGHT_BASE + responsive.verticalPadding, // Dynamic footer spacing
|
|
230
|
+
paddingHorizontal: responsive.horizontalPadding, // Device-based horizontal padding
|
|
225
231
|
}
|
|
226
232
|
]}
|
|
227
233
|
showsVerticalScrollIndicator={false}
|
|
228
234
|
/>
|
|
229
235
|
|
|
230
|
-
{/* Fixed Footer -
|
|
231
|
-
<View style={[styles.footerContainer, { paddingBottom: insets.bottom +
|
|
236
|
+
{/* Fixed Footer - Responsive positioning */}
|
|
237
|
+
<View style={[styles.footerContainer, { paddingBottom: insets.bottom + responsive.verticalPadding }]}>
|
|
232
238
|
<PaywallFooter
|
|
233
239
|
translations={translations}
|
|
234
240
|
legalUrls={legalUrls}
|
|
@@ -236,6 +242,8 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
236
242
|
onPurchase={handlePurchase}
|
|
237
243
|
onRestore={handleRestore}
|
|
238
244
|
onLegalClick={handleLegalUrl}
|
|
245
|
+
isTablet={responsive.isTabletDevice}
|
|
246
|
+
isSmallDevice={responsive.isSmallDevice}
|
|
239
247
|
/>
|
|
240
248
|
</View>
|
|
241
249
|
</View>
|