@umituz/react-native-subscription 3.1.24 → 3.1.26

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.24",
3
+ "version": "3.1.26",
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",
@@ -1,9 +1,8 @@
1
1
  import React from "react";
2
- import { View, TouchableOpacity } from "react-native";
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
5
  import type { PaywallTranslations, PaywallLegalUrls } from "../entities/types";
6
- import { paywallScreenStyles as styles } from "./PaywallScreen.styles";
7
6
 
8
7
  interface PaywallFooterProps {
9
8
  translations: PaywallTranslations;
@@ -13,6 +12,8 @@ interface PaywallFooterProps {
13
12
  onRestore?: () => Promise<void | boolean>;
14
13
  onLegalClick: (url: string | undefined) => void;
15
14
  purchaseButtonText?: string;
15
+ isTablet?: boolean;
16
+ isSmallDevice?: boolean;
16
17
  }
17
18
 
18
19
  export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
@@ -23,58 +24,152 @@ export const PaywallFooter: React.FC<PaywallFooterProps> = React.memo(({
23
24
  onRestore,
24
25
  onLegalClick,
25
26
  purchaseButtonText,
27
+ isTablet = false,
28
+ isSmallDevice = false,
26
29
  }) => {
27
30
  const tokens = useAppDesignTokens();
28
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
+
29
51
  return (
30
- <View style={styles.footer}>
31
- {/* Purchase Button */}
52
+ <View style={footerStyles.container}>
53
+ {/* Purchase Button - Device-responsive */}
32
54
  {onPurchase && (
33
55
  <TouchableOpacity
34
56
  onPress={onPurchase}
35
57
  disabled={isProcessing}
58
+ activeOpacity={0.8}
36
59
  style={[
37
- styles.purchaseButton,
38
- isProcessing && styles.ctaDisabled,
39
- { backgroundColor: tokens.colors.primary }
60
+ footerStyles.purchaseButton,
61
+ {
62
+ backgroundColor: tokens.colors.primary,
63
+ height: buttonHeight,
64
+ paddingVertical: paddingVertical,
65
+ },
66
+ isProcessing && footerStyles.buttonDisabled,
40
67
  ]}
41
68
  >
42
69
  {isProcessing ? (
43
- <AtomicSpinner size="sm" />
70
+ <View style={footerStyles.loadingContainer}>
71
+ <AtomicSpinner size="sm" />
72
+ </View>
44
73
  ) : (
45
- <AtomicText style={[styles.purchaseButtonText, { color: tokens.colors.textPrimary, fontWeight: '700', fontSize: 16 }]}>
74
+ <AtomicText style={[footerStyles.purchaseButtonText, { color: tokens.colors.textPrimary, fontSize }]}>
46
75
  {purchaseButtonText || translations.purchaseButtonText}
47
76
  </AtomicText>
48
77
  )}
49
78
  </TouchableOpacity>
50
79
  )}
51
80
 
52
- {/* Restore Button */}
81
+ {/* Restore Link - Device-spaced */}
53
82
  {onRestore && (
54
- <TouchableOpacity onPress={onRestore} disabled={isProcessing} style={[styles.restoreButton, isProcessing && styles.ctaDisabled]}>
55
- <AtomicText type="bodySmall" style={[styles.footerLink, { color: tokens.colors.textSecondary }]}>
56
- {isProcessing ? translations.processingText : translations.restoreButtonText}
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}
57
91
  </AtomicText>
58
92
  </TouchableOpacity>
59
93
  )}
60
94
 
61
- {/* Legal Links */}
62
- <View style={styles.legalRow}>
95
+ {/* Legal Links - Responsive spacing */}
96
+ <View style={footerStyles.legalContainer}>
63
97
  {legalUrls.termsUrl && (
64
- <TouchableOpacity onPress={() => onLegalClick(legalUrls.termsUrl)}>
65
- <AtomicText type="bodySmall" style={[styles.footerLink, { color: tokens.colors.textSecondary }]}>
98
+ <TouchableOpacity
99
+ onPress={() => onLegalClick(legalUrls.termsUrl)}
100
+ activeOpacity={0.6}
101
+ >
102
+ <AtomicText style={[footerStyles.legalText, { color: tokens.colors.textTertiary }]}>
66
103
  {translations.termsOfServiceText}
67
104
  </AtomicText>
68
105
  </TouchableOpacity>
69
106
  )}
70
107
  {legalUrls.privacyUrl && (
71
- <TouchableOpacity onPress={() => onLegalClick(legalUrls.privacyUrl)}>
72
- <AtomicText type="bodySmall" style={[styles.footerLink, { color: tokens.colors.textSecondary }]}>
73
- {translations.privacyText}
74
- </AtomicText>
75
- </TouchableOpacity>
108
+ <>
109
+ <AtomicText style={footerStyles.legalSeparator}> </AtomicText>
110
+ <TouchableOpacity
111
+ onPress={() => onLegalClick(legalUrls.privacyUrl)}
112
+ activeOpacity={0.6}
113
+ >
114
+ <AtomicText style={[footerStyles.legalText, { color: tokens.colors.textTertiary }]}>
115
+ {translations.privacyText}
116
+ </AtomicText>
117
+ </TouchableOpacity>
118
+ </>
76
119
  )}
77
120
  </View>
78
121
  </View>
79
122
  );
80
123
  });
124
+
125
+ // Modern footer styles
126
+ const footerStyles = StyleSheet.create({
127
+ container: {
128
+ width: '100%',
129
+ paddingHorizontal: 20,
130
+ paddingVertical: 16,
131
+ gap: 12,
132
+ },
133
+ purchaseButton: {
134
+ width: '100%',
135
+ borderRadius: 16,
136
+ justifyContent: 'center',
137
+ alignItems: 'center',
138
+ shadowColor: '#000',
139
+ shadowOffset: { width: 0, height: 4 },
140
+ shadowOpacity: 0.3,
141
+ shadowRadius: 12,
142
+ elevation: 8,
143
+ },
144
+ buttonDisabled: {
145
+ opacity: 0.5,
146
+ },
147
+ loadingContainer: {
148
+ justifyContent: 'center',
149
+ alignItems: 'center',
150
+ },
151
+ purchaseButtonText: {
152
+ fontWeight: '700',
153
+ letterSpacing: 0.5,
154
+ },
155
+ restoreButton: {
156
+ alignItems: 'center',
157
+ },
158
+ restoreText: {
159
+ fontWeight: '500',
160
+ textDecorationLine: 'underline',
161
+ },
162
+ legalContainer: {
163
+ flexDirection: 'row',
164
+ alignItems: 'center',
165
+ justifyContent: 'center',
166
+ flexWrap: 'wrap',
167
+ },
168
+ legalText: {
169
+ lineHeight: 16,
170
+ },
171
+ legalSeparator: {
172
+ color: 'rgba(255, 255, 255, 0.4)',
173
+ marginHorizontal: 4,
174
+ },
175
+ });
@@ -121,46 +121,31 @@ export const paywallScreenStyles = StyleSheet.create({
121
121
  letterSpacing: 0.5,
122
122
  },
123
123
 
124
- // Footer Links
125
- footer: {
124
+ // Footer container with modern design
125
+ footerContainer: {
126
126
  position: "absolute",
127
127
  bottom: 0,
128
128
  left: 0,
129
129
  right: 0,
130
- paddingHorizontal: 24,
131
- paddingTop: 20,
132
- paddingBottom: 40,
133
- borderTopWidth: 1,
134
- borderTopColor: "rgba(255, 255, 255, 0.08)",
130
+ paddingHorizontal: 20,
131
+ paddingTop: 16,
132
+ borderTopWidth: 0.5,
133
+ borderTopColor: "rgba(255, 255, 255, 0.1)",
135
134
  ...Platform.select({
136
135
  ios: {
136
+ backgroundColor: "rgba(0, 0, 0, 0.8)",
137
+ backdropFilter: "blur(20px)",
137
138
  shadowColor: "#000",
138
- shadowOffset: { width: 0, height: -4 },
139
- shadowOpacity: 0.1,
140
- shadowRadius: 12,
139
+ shadowOffset: { width: 0, height: -8 },
140
+ shadowOpacity: 0.3,
141
+ shadowRadius: 20,
141
142
  },
142
143
  android: {
143
- elevation: 8,
144
+ backgroundColor: "#000000",
145
+ elevation: 12,
144
146
  },
145
147
  }),
146
148
  },
147
- purchaseButton: {
148
- borderRadius: 18,
149
- height: 60,
150
- justifyContent: "center",
151
- alignItems: "center",
152
- shadowColor: "#000",
153
- shadowOffset: { width: 0, height: 4 },
154
- shadowOpacity: 0.3,
155
- shadowRadius: 8,
156
- elevation: 6,
157
- marginBottom: 16,
158
- },
159
- purchaseButtonText: {
160
- fontWeight: "700",
161
- letterSpacing: 0.5,
162
- fontSize: 16,
163
- },
164
149
  restoreButton: {
165
150
  paddingVertical: 4,
166
151
  },
@@ -194,6 +179,7 @@ export const paywallScreenStyles = StyleSheet.create({
194
179
  // List
195
180
  listContent: {
196
181
  paddingBottom: 40,
182
+ paddingHorizontal: 20, // Add horizontal padding
197
183
  },
198
184
  loadingContainer: {
199
185
  flex: 1,
@@ -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";
@@ -61,6 +62,7 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
61
62
 
62
63
  const tokens = useAppDesignTokens();
63
64
  const insets = useSafeAreaInsets();
65
+ const responsive = useResponsive();
64
66
 
65
67
  const handleClose = useCallback(() => {
66
68
  if (__DEV__) console.log('[PaywallScreen] 🔙 Closing paywall');
@@ -190,11 +192,11 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
190
192
  <View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
191
193
  <StatusBar barStyle="light-content" />
192
194
 
193
- {/* Absolute Close Button */}
195
+ {/* Absolute Close Button - Responsive positioning */}
194
196
  <View style={{
195
197
  position: 'absolute',
196
198
  top: Math.max(insets.top, 16),
197
- right: 0,
199
+ right: 16,
198
200
  zIndex: 10,
199
201
  }}>
200
202
  <TouchableOpacity
@@ -206,7 +208,7 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
206
208
  </TouchableOpacity>
207
209
  </View>
208
210
 
209
- {/* Main Content */}
211
+ {/* Main Content - Responsive spacing */}
210
212
  <FlatList
211
213
  data={flatData}
212
214
  renderItem={renderItem}
@@ -220,22 +222,27 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
220
222
  contentContainerStyle={[
221
223
  styles.listContent,
222
224
  {
223
- paddingTop: Math.max(insets.top, 20) + 40,
224
- paddingBottom: 220
225
+ paddingTop: Math.max(insets.top, 20) + 60, // Responsive header spacing
226
+ paddingBottom: 280 + responsive.verticalPadding, // Dynamic footer spacing
227
+ paddingHorizontal: responsive.horizontalPadding, // Device-based horizontal padding
225
228
  }
226
229
  ]}
227
230
  showsVerticalScrollIndicator={false}
228
231
  />
229
232
 
230
- {/* Fixed Footer */}
231
- <PaywallFooter
232
- translations={translations}
233
- legalUrls={legalUrls}
234
- isProcessing={isProcessing}
235
- onPurchase={handlePurchase}
236
- onRestore={handleRestore}
237
- onLegalClick={handleLegalUrl}
238
- />
233
+ {/* Fixed Footer - Responsive positioning */}
234
+ <View style={[styles.footerContainer, { paddingBottom: insets.bottom + responsive.verticalPadding }]}>
235
+ <PaywallFooter
236
+ translations={translations}
237
+ legalUrls={legalUrls}
238
+ isProcessing={isProcessing}
239
+ onPurchase={handlePurchase}
240
+ onRestore={handleRestore}
241
+ onLegalClick={handleLegalUrl}
242
+ isTablet={responsive.isTabletDevice}
243
+ isSmallDevice={responsive.isSmallDevice}
244
+ />
245
+ </View>
239
246
  </View>
240
247
  );
241
248
  });