@umituz/react-native-subscription 3.1.26 → 3.1.28
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.28",
|
|
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",
|
|
@@ -2,6 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
3
|
import { AtomicText, AtomicSpinner } from "@umituz/react-native-design-system/atoms";
|
|
4
4
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
5
|
+
import { useResponsive } from "@umituz/react-native-design-system/responsive";
|
|
5
6
|
import type { PaywallTranslations, PaywallLegalUrls } from "../entities/types";
|
|
6
7
|
|
|
7
8
|
interface PaywallFooterProps {
|
|
@@ -12,8 +13,6 @@ interface PaywallFooterProps {
|
|
|
12
13
|
onRestore?: () => Promise<void | boolean>;
|
|
13
14
|
onLegalClick: (url: string | undefined) => void;
|
|
14
15
|
purchaseButtonText?: string;
|
|
15
|
-
isTablet?: boolean;
|
|
16
|
-
isSmallDevice?: boolean;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
|
|
@@ -24,33 +23,45 @@ export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
|
|
|
24
23
|
onRestore,
|
|
25
24
|
onLegalClick,
|
|
26
25
|
purchaseButtonText,
|
|
27
|
-
isTablet = false,
|
|
28
|
-
isSmallDevice = false,
|
|
29
26
|
}) => {
|
|
30
27
|
const tokens = useAppDesignTokens();
|
|
28
|
+
const responsive = useResponsive();
|
|
31
29
|
|
|
32
|
-
//
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}, [isTablet, isSmallDevice]);
|
|
30
|
+
// Base values that will be scaled by spacingMultiplier
|
|
31
|
+
const BASE_BUTTON_HEIGHT = 56;
|
|
32
|
+
const BASE_FONT_SIZE = 17;
|
|
33
|
+
const BASE_PADDING_VERTICAL = 16;
|
|
34
|
+
const BASE_SPACING = 12;
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
// Responsive values using spacingMultiplier
|
|
37
|
+
const buttonHeight = React.useMemo(
|
|
38
|
+
() => Math.round(BASE_BUTTON_HEIGHT * responsive.spacingMultiplier),
|
|
39
|
+
[responsive, BASE_BUTTON_HEIGHT]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const fontSize = React.useMemo(
|
|
43
|
+
() => responsive.getFontSize(BASE_FONT_SIZE),
|
|
44
|
+
[responsive, BASE_FONT_SIZE]
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const paddingVertical = React.useMemo(
|
|
48
|
+
() => Math.round(BASE_PADDING_VERTICAL * responsive.spacingMultiplier),
|
|
49
|
+
[responsive, BASE_PADDING_VERTICAL]
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const spacing = React.useMemo(
|
|
53
|
+
() => Math.round(BASE_SPACING * responsive.spacingMultiplier),
|
|
54
|
+
[responsive, BASE_SPACING]
|
|
55
|
+
);
|
|
44
56
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}, [isTablet, isSmallDevice]);
|
|
57
|
+
const paddingHorizontal = React.useMemo(
|
|
58
|
+
() => responsive.horizontalPadding,
|
|
59
|
+
[responsive]
|
|
60
|
+
);
|
|
50
61
|
|
|
51
62
|
return (
|
|
52
|
-
<View style={footerStyles.container}>
|
|
53
|
-
{/* Purchase Button -
|
|
63
|
+
<View style={[footerStyles.container, { paddingHorizontal, paddingVertical: spacing, gap: spacing }]}>
|
|
64
|
+
{/* Purchase Button - Responsive sizing */}
|
|
54
65
|
{onPurchase && (
|
|
55
66
|
<TouchableOpacity
|
|
56
67
|
onPress={onPurchase}
|
|
@@ -78,13 +89,13 @@ export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
|
|
|
78
89
|
</TouchableOpacity>
|
|
79
90
|
)}
|
|
80
91
|
|
|
81
|
-
{/* Restore Link -
|
|
92
|
+
{/* Restore Link - Responsive spacing */}
|
|
82
93
|
{onRestore && (
|
|
83
94
|
<TouchableOpacity
|
|
84
95
|
onPress={onRestore}
|
|
85
96
|
disabled={isProcessing}
|
|
86
97
|
activeOpacity={0.6}
|
|
87
|
-
style={[footerStyles.restoreButton, { marginTop:
|
|
98
|
+
style={[footerStyles.restoreButton, { marginTop: spacing / 2 }]}
|
|
88
99
|
>
|
|
89
100
|
<AtomicText style={[footerStyles.restoreText, { color: tokens.colors.textSecondary }]}>
|
|
90
101
|
{translations.restoreButtonText}
|
|
@@ -126,9 +137,6 @@ export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
|
|
|
126
137
|
const footerStyles = StyleSheet.create({
|
|
127
138
|
container: {
|
|
128
139
|
width: '100%',
|
|
129
|
-
paddingHorizontal: 20,
|
|
130
|
-
paddingVertical: 16,
|
|
131
|
-
gap: 12,
|
|
132
140
|
},
|
|
133
141
|
purchaseButton: {
|
|
134
142
|
width: '100%',
|
|
@@ -7,15 +7,17 @@ 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";
|
|
11
|
+
import { useResponsive } from "@umituz/react-native-design-system/responsive";
|
|
10
12
|
import { Image } from "expo-image";
|
|
11
13
|
import { PlanCard } from "./PlanCard";
|
|
12
14
|
import type { PaywallListItem } from "../utils/paywallLayoutUtils";
|
|
15
|
+
import type { PaywallTranslations } from "../entities/types";
|
|
13
16
|
import { paywallScreenStyles as styles } from "./PaywallScreen.styles";
|
|
14
17
|
|
|
15
18
|
interface PaywallRenderItemProps {
|
|
16
19
|
item: PaywallListItem;
|
|
17
|
-
|
|
18
|
-
translations: any;
|
|
20
|
+
translations: PaywallTranslations;
|
|
19
21
|
heroImage?: ImageSourcePropType;
|
|
20
22
|
selectedPlanId?: string;
|
|
21
23
|
bestValueIdentifier?: string;
|
|
@@ -26,7 +28,6 @@ interface PaywallRenderItemProps {
|
|
|
26
28
|
|
|
27
29
|
export const PaywallRenderItem: React.FC<PaywallRenderItemProps> = React.memo(({
|
|
28
30
|
item,
|
|
29
|
-
tokens,
|
|
30
31
|
translations,
|
|
31
32
|
heroImage,
|
|
32
33
|
selectedPlanId,
|
|
@@ -35,16 +36,42 @@ export const PaywallRenderItem: React.FC<PaywallRenderItemProps> = React.memo(({
|
|
|
35
36
|
creditsLabel,
|
|
36
37
|
onSelectPlan,
|
|
37
38
|
}) => {
|
|
39
|
+
const tokens = useAppDesignTokens();
|
|
40
|
+
const responsive = useResponsive();
|
|
41
|
+
|
|
42
|
+
// Responsive spacing
|
|
43
|
+
const spacing = React.useMemo(
|
|
44
|
+
() => Math.round(16 * responsive.spacingMultiplier),
|
|
45
|
+
[responsive]
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Responsive feature icon size
|
|
49
|
+
const featureIconSize = React.useMemo(
|
|
50
|
+
() => responsive.getIconSize(30),
|
|
51
|
+
[responsive]
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Responsive hero image size
|
|
55
|
+
const heroImageSize = React.useMemo(
|
|
56
|
+
() => responsive.getIconSize(120),
|
|
57
|
+
[responsive]
|
|
58
|
+
);
|
|
59
|
+
|
|
38
60
|
if (!translations) return null;
|
|
39
61
|
|
|
40
62
|
switch (item.type) {
|
|
41
63
|
case 'HEADER':
|
|
42
64
|
return (
|
|
43
65
|
<View key="header">
|
|
44
|
-
{/* Hero Image */}
|
|
66
|
+
{/* Hero Image - Responsive sizing */}
|
|
45
67
|
{heroImage && (
|
|
46
68
|
<View style={styles.heroContainer}>
|
|
47
|
-
<Image
|
|
69
|
+
<Image
|
|
70
|
+
source={heroImage}
|
|
71
|
+
style={[styles.heroImage, { width: heroImageSize, height: heroImageSize, borderRadius: heroImageSize * 0.25 }]}
|
|
72
|
+
contentFit="cover"
|
|
73
|
+
transition={200}
|
|
74
|
+
/>
|
|
48
75
|
</View>
|
|
49
76
|
)}
|
|
50
77
|
|
|
@@ -64,7 +91,7 @@ export const PaywallRenderItem: React.FC<PaywallRenderItemProps> = React.memo(({
|
|
|
64
91
|
|
|
65
92
|
case 'FEATURE_HEADER':
|
|
66
93
|
return (
|
|
67
|
-
<View key="feat-header" style={styles.sectionHeader}>
|
|
94
|
+
<View key="feat-header" style={[styles.sectionHeader, { marginTop: spacing * 1.5 }]}>
|
|
68
95
|
<AtomicText type="labelLarge" style={[styles.sectionTitle, { color: tokens.colors.textSecondary }]}>
|
|
69
96
|
{translations.featuresTitle || "WHAT'S INCLUDED"}
|
|
70
97
|
</AtomicText>
|
|
@@ -73,9 +100,13 @@ export const PaywallRenderItem: React.FC<PaywallRenderItemProps> = React.memo(({
|
|
|
73
100
|
|
|
74
101
|
case 'FEATURE':
|
|
75
102
|
return (
|
|
76
|
-
<View key={`feat-${item.feature.text}`} style={[styles.featureRow, {
|
|
77
|
-
<View style={[styles.featureIcon, { backgroundColor: tokens.colors.primary }]}>
|
|
78
|
-
<AtomicIcon
|
|
103
|
+
<View key={`feat-${item.feature.text}`} style={[styles.featureRow, { marginBottom: spacing }]}>
|
|
104
|
+
<View style={[styles.featureIcon, { width: featureIconSize, height: featureIconSize, backgroundColor: tokens.colors.primary }]}>
|
|
105
|
+
<AtomicIcon
|
|
106
|
+
name={item.feature.icon}
|
|
107
|
+
customSize={responsive.getFontSize(16)}
|
|
108
|
+
customColor={tokens.colors.onPrimary}
|
|
109
|
+
/>
|
|
79
110
|
</View>
|
|
80
111
|
<AtomicText type="bodyMedium" style={[styles.featureText, { color: tokens.colors.textPrimary }]}>
|
|
81
112
|
{item.feature.text}
|
|
@@ -85,7 +116,7 @@ export const PaywallRenderItem: React.FC<PaywallRenderItemProps> = React.memo(({
|
|
|
85
116
|
|
|
86
117
|
case 'PLAN_HEADER':
|
|
87
118
|
return (
|
|
88
|
-
<View key="plan-header" style={styles.sectionHeader}>
|
|
119
|
+
<View key="plan-header" style={[styles.sectionHeader, { marginTop: spacing * 1.5 }]}>
|
|
89
120
|
<AtomicText type="labelLarge" style={[styles.sectionTitle, { color: tokens.colors.textSecondary }]}>
|
|
90
121
|
{translations.plansTitle || "CHOOSE YOUR PLAN"}
|
|
91
122
|
</AtomicText>
|
|
@@ -30,6 +30,10 @@ import {
|
|
|
30
30
|
import { hasItems } from "../../../shared/utils/arrayUtils";
|
|
31
31
|
import { PaywallRenderItem } from "./PaywallScreen.renderItem";
|
|
32
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
|
+
|
|
33
37
|
export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) => {
|
|
34
38
|
const navigation = useNavigation();
|
|
35
39
|
|
|
@@ -148,7 +152,6 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
148
152
|
return (
|
|
149
153
|
<PaywallRenderItem
|
|
150
154
|
item={item}
|
|
151
|
-
tokens={tokens}
|
|
152
155
|
translations={translations}
|
|
153
156
|
heroImage={heroImage}
|
|
154
157
|
selectedPlanId={selectedPlanId ?? undefined}
|
|
@@ -158,7 +161,7 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
158
161
|
onSelectPlan={setSelectedPlanId}
|
|
159
162
|
/>
|
|
160
163
|
);
|
|
161
|
-
}, [
|
|
164
|
+
}, [translations, heroImage, selectedPlanId, bestValueIdentifier, creditAmounts, creditsLabel, setSelectedPlanId]);
|
|
162
165
|
|
|
163
166
|
// Performance Optimization: getItemLayout for FlatList
|
|
164
167
|
const getItemLayout = useCallback((_data: ArrayLike<PaywallListItem> | null | undefined, index: number) => {
|
|
@@ -222,8 +225,8 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
222
225
|
contentContainerStyle={[
|
|
223
226
|
styles.listContent,
|
|
224
227
|
{
|
|
225
|
-
paddingTop: Math.max(insets.top, 20) +
|
|
226
|
-
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
|
|
227
230
|
paddingHorizontal: responsive.horizontalPadding, // Device-based horizontal padding
|
|
228
231
|
}
|
|
229
232
|
]}
|
|
@@ -239,8 +242,6 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
|
|
|
239
242
|
onPurchase={handlePurchase}
|
|
240
243
|
onRestore={handleRestore}
|
|
241
244
|
onLegalClick={handleLegalUrl}
|
|
242
|
-
isTablet={responsive.isTabletDevice}
|
|
243
|
-
isSmallDevice={responsive.isSmallDevice}
|
|
244
245
|
/>
|
|
245
246
|
</View>
|
|
246
247
|
</View>
|