@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.26",
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
- // 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]);
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
- const fontSize = React.useMemo(() => {
40
- if (isTablet) return 19;
41
- if (isSmallDevice) return 16;
42
- return 17;
43
- }, [isTablet, isSmallDevice]);
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 paddingVertical = React.useMemo(() => {
46
- if (isTablet) return 20;
47
- if (isSmallDevice) return 12;
48
- return 16;
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 - Device-responsive */}
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 - Device-spaced */}
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: isTablet ? 16 : 8 }]}
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
- tokens: any;
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 source={heroImage} style={styles.heroImage} contentFit="cover" transition={200} />
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, { marginHorizontal: 24, marginBottom: 16 }]}>
77
- <View style={[styles.featureIcon, { backgroundColor: tokens.colors.primary }]}>
78
- <AtomicIcon name={item.feature.icon} customSize={16} customColor={tokens.colors.onPrimary} />
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>
@@ -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,
@@ -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
- }, [tokens, translations, heroImage, selectedPlanId, bestValueIdentifier, creditAmounts, creditsLabel, setSelectedPlanId]);
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) + 60, // Responsive header spacing
226
- paddingBottom: 280 + responsive.verticalPadding, // Dynamic footer spacing
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>