@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.25",
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 - Large and prominent */}
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
- { backgroundColor: tokens.colors.primary },
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 - Minimal */}
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 - Minimal */}
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
- tokens: any;
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={[styles.featureRow, { marginHorizontal: 24, marginBottom: 16 }]}>
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>
@@ -61,6 +61,8 @@ export const paywallScreenStyles = StyleSheet.create({
61
61
  flexDirection: "row",
62
62
  alignItems: "center",
63
63
  gap: 14,
64
+ marginHorizontal: 24,
65
+ marginBottom: 16,
64
66
  },
65
67
  featureIcon: {
66
68
  width: 30,
@@ -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
- }, [tokens, translations, heroImage, selectedPlanId, bestValueIdentifier, creditAmounts, creditsLabel, setSelectedPlanId]);
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: 0,
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) + 50,
224
- paddingBottom: 280 // Increased for footer
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 - Improved positioning */}
231
- <View style={[styles.footerContainer, { paddingBottom: insets.bottom + 20 }]}>
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>