@umituz/react-native-subscription 3.1.27 → 3.1.29

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.27",
3
+ "version": "3.1.29",
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
+ );
44
46
 
45
- const paddingVertical = React.useMemo(() => {
46
- if (isTablet) return 20;
47
- if (isSmallDevice) return 12;
48
- return 16;
49
- }, [isTablet, isSmallDevice]);
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
+ );
56
+
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,22 +89,25 @@ export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
78
89
  </TouchableOpacity>
79
90
  )}
80
91
 
81
- {/* Restore Link - Device-spaced */}
82
- {onRestore && (
83
- <TouchableOpacity
84
- onPress={onRestore}
85
- disabled={isProcessing}
86
- activeOpacity={0.6}
87
- style={[footerStyles.restoreButton, { marginTop: isTablet ? 16 : 8 }]}
88
- >
89
- <AtomicText style={[footerStyles.restoreText, { color: tokens.colors.textSecondary }]}>
90
- {translations.restoreButtonText}
91
- </AtomicText>
92
- </TouchableOpacity>
93
- )}
92
+ {/* Footer Links - Premium inline layout */}
93
+ <View style={[footerStyles.legalContainer, { marginTop: spacing / 2 }]}>
94
+ {onRestore && (
95
+ <TouchableOpacity
96
+ onPress={onRestore}
97
+ disabled={isProcessing}
98
+ activeOpacity={0.6}
99
+ style={footerStyles.restoreButton}
100
+ >
101
+ <AtomicText style={[footerStyles.legalText, { color: tokens.colors.textSecondary, fontWeight: '600' }]}>
102
+ {translations.restoreButtonText}
103
+ </AtomicText>
104
+ </TouchableOpacity>
105
+ )}
106
+
107
+ {(onRestore && legalUrls.termsUrl) && (
108
+ <AtomicText style={footerStyles.legalSeparator}> • </AtomicText>
109
+ )}
94
110
 
95
- {/* Legal Links - Responsive spacing */}
96
- <View style={footerStyles.legalContainer}>
97
111
  {legalUrls.termsUrl && (
98
112
  <TouchableOpacity
99
113
  onPress={() => onLegalClick(legalUrls.termsUrl)}
@@ -104,6 +118,7 @@ export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
104
118
  </AtomicText>
105
119
  </TouchableOpacity>
106
120
  )}
121
+
107
122
  {legalUrls.privacyUrl && (
108
123
  <>
109
124
  <AtomicText style={footerStyles.legalSeparator}> • </AtomicText>
@@ -126,9 +141,6 @@ export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
126
141
  const footerStyles = StyleSheet.create({
127
142
  container: {
128
143
  width: '100%',
129
- paddingHorizontal: 20,
130
- paddingVertical: 16,
131
- gap: 12,
132
144
  },
133
145
  purchaseButton: {
134
146
  width: '100%',
@@ -8,6 +8,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
10
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
11
+ import { useResponsive } from "@umituz/react-native-design-system/responsive";
11
12
  import { Image } from "expo-image";
12
13
  import { PlanCard } from "./PlanCard";
13
14
  import type { PaywallListItem } from "../utils/paywallLayoutUtils";
@@ -36,6 +37,25 @@ export const PaywallRenderItem: React.FC<PaywallRenderItemProps> = React.memo(({
36
37
  onSelectPlan,
37
38
  }) => {
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 - use spacing multiplier directly to avoid max/min constraints from getIconSize
49
+ const featureIconSize = React.useMemo(
50
+ () => Math.round(30 * responsive.spacingMultiplier),
51
+ [responsive]
52
+ );
53
+
54
+ // Responsive hero image size
55
+ const heroImageSize = React.useMemo(
56
+ () => responsive.getIconSize(120),
57
+ [responsive]
58
+ );
39
59
 
40
60
  if (!translations) return null;
41
61
 
@@ -43,10 +63,15 @@ export const PaywallRenderItem: React.FC<PaywallRenderItemProps> = React.memo(({
43
63
  case 'HEADER':
44
64
  return (
45
65
  <View key="header">
46
- {/* Hero Image */}
66
+ {/* Hero Image - Responsive sizing */}
47
67
  {heroImage && (
48
68
  <View style={styles.heroContainer}>
49
- <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
+ />
50
75
  </View>
51
76
  )}
52
77
 
@@ -66,7 +91,7 @@ export const PaywallRenderItem: React.FC<PaywallRenderItemProps> = React.memo(({
66
91
 
67
92
  case 'FEATURE_HEADER':
68
93
  return (
69
- <View key="feat-header" style={styles.sectionHeader}>
94
+ <View key="feat-header" style={[styles.sectionHeader, { marginTop: spacing * 1.5 }]}>
70
95
  <AtomicText type="labelLarge" style={[styles.sectionTitle, { color: tokens.colors.textSecondary }]}>
71
96
  {translations.featuresTitle || "WHAT'S INCLUDED"}
72
97
  </AtomicText>
@@ -75,9 +100,13 @@ export const PaywallRenderItem: React.FC<PaywallRenderItemProps> = React.memo(({
75
100
 
76
101
  case 'FEATURE':
77
102
  return (
78
- <View key={`feat-${item.feature.text}`} style={styles.featureRow}>
79
- <View style={[styles.featureIcon, { backgroundColor: tokens.colors.primary }]}>
80
- <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
+ />
81
110
  </View>
82
111
  <AtomicText type="bodyMedium" style={[styles.featureText, { color: tokens.colors.textPrimary }]}>
83
112
  {item.feature.text}
@@ -87,7 +116,7 @@ export const PaywallRenderItem: React.FC<PaywallRenderItemProps> = React.memo(({
87
116
 
88
117
  case 'PLAN_HEADER':
89
118
  return (
90
- <View key="plan-header" style={styles.sectionHeader}>
119
+ <View key="plan-header" style={[styles.sectionHeader, { marginTop: spacing * 1.5 }]}>
91
120
  <AtomicText type="labelLarge" style={[styles.sectionTitle, { color: tokens.colors.textSecondary }]}>
92
121
  {translations.plansTitle || "CHOOSE YOUR PLAN"}
93
122
  </AtomicText>
@@ -242,8 +242,6 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
242
242
  onPurchase={handlePurchase}
243
243
  onRestore={handleRestore}
244
244
  onLegalClick={handleLegalUrl}
245
- isTablet={responsive.isTabletDevice}
246
- isSmallDevice={responsive.isSmallDevice}
247
245
  />
248
246
  </View>
249
247
  </View>