@umituz/react-native-settings 4.20.3 → 4.20.5

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-settings",
3
- "version": "4.20.3",
3
+ "version": "4.20.5",
4
4
  "description": "Complete settings hub for React Native apps - consolidated package with settings, about, legal, appearance, feedback, FAQs, and rating",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Appearance Screen
3
3
  *
4
- * Screen for managing appearance settings including theme mode and custom colors
5
- * Single Responsibility: Presentation orchestration only
4
+ * Screen for managing appearance settings including theme mode and custom colors.
5
+ * Uses ScreenLayout from design system for consistent UI.
6
6
  */
7
7
 
8
8
  import React, { useMemo, useCallback } from "react";
9
- import { ScrollView, StyleSheet, View } from "react-native";
9
+ import { ScreenLayout } from "@umituz/react-native-design-system";
10
10
  import { useAppDesignTokens } from "@umituz/react-native-design-system";
11
11
  import { useAppearance, useAppearanceActions } from "../../hooks";
12
12
  import {
@@ -16,42 +16,14 @@ import {
16
16
  AppearancePreview,
17
17
  type ThemeOptionConfig,
18
18
  } from "../components";
19
-
20
19
  import type { AppearanceTexts } from "../../types";
21
20
 
22
21
  export interface AppearanceScreenProps {
23
- /** Texts for localization */
24
22
  texts?: AppearanceTexts;
25
-
26
- /**
27
- * Custom header component to override default header
28
- */
29
23
  headerComponent?: React.ReactNode;
30
-
31
- /**
32
- * Show/hide theme mode section
33
- */
34
24
  showThemeSection?: boolean;
35
-
36
- /**
37
- * Show/hide custom colors section
38
- */
39
25
  showColorsSection?: boolean;
40
-
41
- /**
42
- * Show/hide preview section
43
- */
44
26
  showPreviewSection?: boolean;
45
-
46
- /**
47
- * Custom container style
48
- */
49
- containerStyle?: any;
50
-
51
- /**
52
- * Custom content container style
53
- */
54
- contentContainerStyle?: any;
55
27
  }
56
28
 
57
29
  export const AppearanceScreen: React.FC<AppearanceScreenProps> = ({
@@ -60,8 +32,6 @@ export const AppearanceScreen: React.FC<AppearanceScreenProps> = ({
60
32
  showThemeSection = true,
61
33
  showColorsSection = true,
62
34
  showPreviewSection = true,
63
- containerStyle,
64
- contentContainerStyle,
65
35
  }) => {
66
36
  const tokens = useAppDesignTokens();
67
37
  const { themeMode } = useAppearance();
@@ -72,10 +42,6 @@ export const AppearanceScreen: React.FC<AppearanceScreenProps> = ({
72
42
  handleResetColors,
73
43
  } = useAppearanceActions();
74
44
 
75
- // Memoize styles to prevent unnecessary re-creation
76
- const styles = useMemo(() => getStyles(tokens), [tokens]);
77
-
78
- // Memoize header to prevent unnecessary re-renders
79
45
  const headerComponentMemo = useMemo(() => {
80
46
  return (
81
47
  headerComponent || (
@@ -88,7 +54,6 @@ export const AppearanceScreen: React.FC<AppearanceScreenProps> = ({
88
54
  );
89
55
  }, [headerComponent, tokens, texts?.title, texts?.subtitle]);
90
56
 
91
- // Stable callback for color change to prevent infinite re-renders
92
57
  const stableHandleColorChange = useCallback(
93
58
  (key: keyof typeof localCustomColors, color: string) => {
94
59
  handleColorChange(key, color);
@@ -96,15 +61,11 @@ export const AppearanceScreen: React.FC<AppearanceScreenProps> = ({
96
61
  [handleColorChange]
97
62
  );
98
63
 
99
- // Memoize sections to prevent unnecessary re-renders
100
64
  const themeSectionMemo = useMemo(() => {
101
65
  if (!showThemeSection) return null;
102
66
 
103
- // Construct themes from texts prop
104
- // This adheres to "Package Driven Design" where content is driven by the consumer (App)
105
67
  const themes: ThemeOptionConfig[] = [];
106
68
 
107
- // We only add the theme option if the corresponding text config is provided
108
69
  if (texts?.lightMode) {
109
70
  themes.push({
110
71
  mode: 'light' as const,
@@ -125,9 +86,6 @@ export const AppearanceScreen: React.FC<AppearanceScreenProps> = ({
125
86
  });
126
87
  }
127
88
 
128
- // If no texts provided, themes array is empty, section will return null.
129
- // This forces the consuming app to provide the texts.
130
-
131
89
  return (
132
90
  <ThemeModeSection
133
91
  tokens={tokens}
@@ -194,34 +152,11 @@ export const AppearanceScreen: React.FC<AppearanceScreenProps> = ({
194
152
  ]);
195
153
 
196
154
  return (
197
- <View style={[styles.container, containerStyle]}>
198
- <ScrollView
199
- style={styles.scrollView}
200
- contentContainerStyle={[styles.scrollContent, contentContainerStyle]}
201
- showsVerticalScrollIndicator={false}
202
- removeClippedSubviews={true} // Performance optimization for long lists
203
- scrollEventThrottle={16} // 60fps throttling
204
- >
205
- {headerComponentMemo}
206
- {themeSectionMemo}
207
- {colorsSectionMemo}
208
- {previewSectionMemo}
209
- </ScrollView>
210
- </View>
155
+ <ScreenLayout hideScrollIndicator>
156
+ {headerComponentMemo}
157
+ {themeSectionMemo}
158
+ {colorsSectionMemo}
159
+ {previewSectionMemo}
160
+ </ScreenLayout>
211
161
  );
212
162
  };
213
-
214
- const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
215
- StyleSheet.create({
216
- container: {
217
- flex: 1,
218
- backgroundColor: tokens.colors.backgroundPrimary,
219
- },
220
- scrollView: {
221
- flex: 1,
222
- },
223
- scrollContent: {
224
- padding: tokens.spacing.md,
225
- paddingBottom: tokens.spacing.xl,
226
- },
227
- });
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Legal Documents List Component
3
+ *
4
+ * Displays list of legal documents (Privacy, Terms, EULA).
5
+ */
6
+
7
+ import React from "react";
8
+ import { View, StyleSheet } from "react-native";
9
+ import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
10
+ import { LegalItem } from "./LegalItem";
11
+ import { UrlHandlerService } from "../../domain/services/UrlHandlerService";
12
+
13
+ interface LegalDocumentsListProps {
14
+ documentsHeader?: string;
15
+ privacyTitle?: string;
16
+ privacyDescription?: string;
17
+ termsTitle?: string;
18
+ termsDescription?: string;
19
+ eulaTitle?: string;
20
+ eulaDescription?: string;
21
+ onPrivacyPress?: () => void;
22
+ onTermsPress?: () => void;
23
+ onEulaPress?: () => void;
24
+ eulaUrl?: string;
25
+ }
26
+
27
+ export const LegalDocumentsList: React.FC<LegalDocumentsListProps> = React.memo(({
28
+ documentsHeader,
29
+ privacyTitle,
30
+ privacyDescription,
31
+ termsTitle,
32
+ termsDescription,
33
+ eulaTitle,
34
+ eulaDescription,
35
+ onPrivacyPress,
36
+ onTermsPress,
37
+ onEulaPress,
38
+ eulaUrl,
39
+ }) => {
40
+ const tokens = useAppDesignTokens();
41
+
42
+ const handleEulaPress = React.useCallback(async () => {
43
+ if (__DEV__) {
44
+ console.log('LegalDocumentsList: EULA pressed', { eulaUrl });
45
+ }
46
+
47
+ if (onEulaPress) {
48
+ onEulaPress();
49
+ } else if (eulaUrl) {
50
+ try {
51
+ await UrlHandlerService.openUrl(eulaUrl);
52
+ } catch (error) {
53
+ if (__DEV__) {
54
+ console.error('LegalDocumentsList: Error opening EULA URL', error);
55
+ }
56
+ }
57
+ }
58
+ }, [onEulaPress, eulaUrl]);
59
+
60
+ const showPrivacy = !!(onPrivacyPress && privacyTitle);
61
+ const showTerms = !!(onTermsPress && termsTitle);
62
+ const showEula = !!((onEulaPress || eulaUrl) && eulaTitle);
63
+
64
+ return (
65
+ <View style={[styles.section, { marginTop: tokens.spacing.md }]}>
66
+ {documentsHeader && (
67
+ <AtomicText
68
+ type="labelLarge"
69
+ color="textSecondary"
70
+ style={[styles.sectionHeader, {
71
+ marginBottom: tokens.spacing.sm,
72
+ paddingHorizontal: tokens.spacing.md,
73
+ }]}
74
+ >
75
+ {documentsHeader}
76
+ </AtomicText>
77
+ )}
78
+
79
+ {showPrivacy && (
80
+ <LegalItem
81
+ iconName="shield"
82
+ title={privacyTitle!}
83
+ description={privacyDescription}
84
+ onPress={onPrivacyPress}
85
+ testID="privacy-policy-item"
86
+ />
87
+ )}
88
+
89
+ {showTerms && (
90
+ <LegalItem
91
+ iconName="document-text"
92
+ title={termsTitle!}
93
+ description={termsDescription}
94
+ onPress={onTermsPress}
95
+ testID="terms-of-service-item"
96
+ />
97
+ )}
98
+
99
+ {showEula && (
100
+ <LegalItem
101
+ iconName="document"
102
+ title={eulaTitle!}
103
+ description={eulaDescription}
104
+ onPress={handleEulaPress}
105
+ testID="eula-item"
106
+ />
107
+ )}
108
+ </View>
109
+ );
110
+ });
111
+
112
+ const styles = StyleSheet.create({
113
+ section: {},
114
+ sectionHeader: {},
115
+ });
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Legal Screen Header Component
3
+ *
4
+ * Displays title and description for legal screen.
5
+ */
6
+
7
+ import React from "react";
8
+ import { View, StyleSheet } from "react-native";
9
+ import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
10
+
11
+ interface LegalScreenHeaderProps {
12
+ title?: string;
13
+ description?: string;
14
+ }
15
+
16
+ export const LegalScreenHeader: React.FC<LegalScreenHeaderProps> = React.memo(({
17
+ title,
18
+ description,
19
+ }) => {
20
+ const tokens = useAppDesignTokens();
21
+
22
+ if (!title) return null;
23
+
24
+ return (
25
+ <View style={[styles.header, { paddingBottom: tokens.spacing.lg, paddingTop: tokens.spacing.md }]}>
26
+ <AtomicText type="headlineLarge" color="textPrimary">
27
+ {title}
28
+ </AtomicText>
29
+ {description && (
30
+ <AtomicText
31
+ type="bodyMedium"
32
+ color="textSecondary"
33
+ style={[styles.subtitle, { marginTop: tokens.spacing.xs }]}
34
+ >
35
+ {description}
36
+ </AtomicText>
37
+ )}
38
+ </View>
39
+ );
40
+ });
41
+
42
+ const styles = StyleSheet.create({
43
+ header: {},
44
+ subtitle: {},
45
+ });
@@ -0,0 +1,5 @@
1
+ export { LegalItem } from "./LegalItem";
2
+ export { LegalLinks } from "./LegalLinks";
3
+ export { LegalSection } from "./LegalSection";
4
+ export { LegalScreenHeader } from "./LegalScreenHeader";
5
+ export { LegalDocumentsList } from "./LegalDocumentsList";
@@ -2,236 +2,66 @@
2
2
  * Legal Screen Component
3
3
  *
4
4
  * Single Responsibility: Layout and orchestration of legal documents list
5
- * Delegates item rendering to LegalItem component
6
- *
7
- * All text content is passed as props (no localization dependency).
5
+ * Uses ScreenLayout from design system for consistent UI.
8
6
  */
9
7
 
10
8
  import React from "react";
11
- import { View, StyleSheet } from "react-native";
12
- import { useAppDesignTokens, type DesignTokens } from "@umituz/react-native-design-system";
13
- import { AtomicText } from "@umituz/react-native-design-system";
14
9
  import { ScreenLayout } from "@umituz/react-native-design-system";
15
- import { LegalItem } from "../components/LegalItem";
16
- import { UrlHandlerService } from "../../domain/services/UrlHandlerService";
17
- import { ContentValidationService } from "../../domain/services/ContentValidationService";
18
- import { StyleCacheService } from "../../domain/services/StyleCacheService";
10
+ import { LegalScreenHeader } from "../components/LegalScreenHeader";
11
+ import { LegalDocumentsList } from "../components/LegalDocumentsList";
19
12
 
20
13
  export interface LegalScreenProps {
21
- /**
22
- * Title of the screen
23
- */
24
14
  title?: string;
25
- /**
26
- * Description/subtitle text
27
- */
28
15
  description?: string;
29
- /**
30
- * Header text for documents section
31
- */
32
16
  documentsHeader?: string;
33
- /**
34
- * Privacy Policy button text
35
- */
36
17
  privacyTitle?: string;
37
- /**
38
- * Privacy Policy description
39
- */
40
18
  privacyDescription?: string;
41
- /**
42
- * Terms of Service button text
43
- */
44
19
  termsTitle?: string;
45
- /**
46
- * Terms of Service description
47
- */
48
20
  termsDescription?: string;
49
- /**
50
- * EULA button text
51
- */
52
21
  eulaTitle?: string;
53
- /**
54
- * EULA description
55
- */
56
22
  eulaDescription?: string;
57
- /**
58
- * Callback when Privacy Policy is pressed
59
- */
60
23
  onPrivacyPress?: () => void;
61
- /**
62
- * Callback when Terms of Service is pressed
63
- */
64
24
  onTermsPress?: () => void;
65
- /**
66
- * Callback when EULA is pressed
67
- * Icon name from Feather library (e.g., "shield", "file-text", "file") EULA URL
68
- */
69
25
  onEulaPress?: () => void;
70
- /**
71
- * EULA URL (defaults to Apple's standard EULA)
72
- */
73
26
  eulaUrl?: string;
74
- /**
75
- * Test ID for E2E testing
76
- */
77
27
  testID?: string;
78
28
  }
79
29
 
80
- export const LegalScreen: React.FC<LegalScreenProps> = React.memo(({
81
- title,
82
- description,
83
- documentsHeader,
84
- privacyTitle,
85
- privacyDescription,
86
- termsTitle,
87
- termsDescription,
88
- eulaTitle,
89
- eulaDescription,
90
- onPrivacyPress,
91
- onTermsPress,
92
- onEulaPress,
93
- eulaUrl,
94
- testID = "legal-screen",
95
- }) => {
96
- const tokens = useAppDesignTokens();
97
-
98
- // Memoize styles to prevent recreation on every render
99
- const styles = React.useMemo(() => {
100
- const cacheKey = StyleCacheService.createTokenCacheKey(tokens);
101
- return StyleCacheService.getCachedStyles(
102
- 'LegalScreen',
103
- cacheKey,
104
- () => createLegalScreenStyles(tokens)
105
- );
106
- }, [tokens]);
107
-
108
- // Memoize EULA press handler to prevent child re-renders
109
- const handleEulaPress = React.useCallback(async () => {
110
- if (__DEV__) {
111
- console.log('LegalScreen: EULA pressed', { eulaUrl });
112
- }
113
-
114
- if (onEulaPress) {
115
- onEulaPress();
116
- } else if (eulaUrl) {
117
- try {
118
- await UrlHandlerService.openUrl(eulaUrl);
119
- } catch (error) {
120
- if (__DEV__) {
121
- console.error('LegalScreen: Error opening EULA URL', error);
122
- }
123
- }
124
- }
125
- }, [onEulaPress, eulaUrl]);
126
-
127
- // Memoize conditional rendering to prevent unnecessary re-renders
128
- const showHeader = React.useMemo(() => !!(title), [title]);
129
- const showDescription = React.useMemo(() => !!(description), [description]);
130
- const showSectionHeader = React.useMemo(() => !!(documentsHeader), [documentsHeader]);
131
- const showPrivacy = React.useMemo(() =>
132
- ContentValidationService.shouldShowLegalItem(onPrivacyPress, privacyTitle),
133
- [onPrivacyPress, privacyTitle]
134
- );
135
- const showTerms = React.useMemo(() =>
136
- ContentValidationService.shouldShowLegalItem(onTermsPress, termsTitle),
137
- [onTermsPress, termsTitle]
138
- );
139
- const showEula = React.useMemo(() =>
140
- !!((onEulaPress || eulaUrl) && eulaTitle),
141
- [onEulaPress, eulaUrl, eulaTitle]
142
- );
143
-
144
- // Memoize header content
145
- const headerContent = React.useMemo(() => {
146
- if (!showHeader) return null;
147
-
148
- return (
149
- <View style={styles.header}>
150
- <AtomicText type="headlineLarge" color="textPrimary">
151
- {title}
152
- </AtomicText>
153
- {showDescription && (
154
- <AtomicText
155
- type="bodyMedium"
156
- color="textSecondary"
157
- style={styles.headerSubtitle}
158
- >
159
- {description}
160
- </AtomicText>
161
- )}
162
- </View>
163
- );
164
- }, [showHeader, showDescription, styles.header, styles.headerSubtitle, title, description]);
30
+ export const LegalScreen: React.FC<LegalScreenProps> = React.memo((props) => {
31
+ const {
32
+ title,
33
+ description,
34
+ documentsHeader,
35
+ privacyTitle,
36
+ privacyDescription,
37
+ termsTitle,
38
+ termsDescription,
39
+ eulaTitle,
40
+ eulaDescription,
41
+ onPrivacyPress,
42
+ onTermsPress,
43
+ onEulaPress,
44
+ eulaUrl,
45
+ testID = "legal-screen",
46
+ } = props;
165
47
 
166
48
  return (
167
49
  <ScreenLayout testID={testID} hideScrollIndicator>
168
- {/* Header */}
169
- {headerContent}
170
-
171
- {/* Legal Documents Section */}
172
- <View style={styles.section}>
173
- {showSectionHeader && (
174
- <AtomicText
175
- type="labelLarge"
176
- color="textSecondary"
177
- style={styles.sectionHeader}
178
- >
179
- {documentsHeader}
180
- </AtomicText>
181
- )}
182
-
183
- {/* Privacy Policy */}
184
- {showPrivacy && (
185
- <LegalItem
186
- iconName="shield"
187
- title={privacyTitle!}
188
- description={privacyDescription}
189
- onPress={onPrivacyPress}
190
- testID="privacy-policy-item"
191
- />
192
- )}
193
-
194
- {/* Terms of Service */}
195
- {showTerms && (
196
- <LegalItem
197
- iconName="document-text"
198
- title={termsTitle!}
199
- description={termsDescription}
200
- onPress={onTermsPress}
201
- testID="terms-of-service-item"
202
- />
203
- )}
204
-
205
- {/* EULA */}
206
- {showEula && (
207
- <LegalItem
208
- iconName="document"
209
- title={eulaTitle!}
210
- description={eulaDescription}
211
- onPress={handleEulaPress}
212
- testID="eula-item"
213
- />
214
- )}
215
- </View>
50
+ <LegalScreenHeader title={title} description={description} />
51
+
52
+ <LegalDocumentsList
53
+ documentsHeader={documentsHeader}
54
+ privacyTitle={privacyTitle}
55
+ privacyDescription={privacyDescription}
56
+ termsTitle={termsTitle}
57
+ termsDescription={termsDescription}
58
+ eulaTitle={eulaTitle}
59
+ eulaDescription={eulaDescription}
60
+ onPrivacyPress={onPrivacyPress}
61
+ onTermsPress={onTermsPress}
62
+ onEulaPress={onEulaPress}
63
+ eulaUrl={eulaUrl}
64
+ />
216
65
  </ScreenLayout>
217
66
  );
218
67
  });
219
-
220
- const createLegalScreenStyles = (tokens: DesignTokens) => {
221
- return StyleSheet.create({
222
- header: {
223
- paddingBottom: tokens.spacing.lg,
224
- paddingTop: tokens.spacing.md,
225
- },
226
- headerSubtitle: {
227
- marginTop: tokens.spacing.xs,
228
- },
229
- section: {
230
- marginTop: tokens.spacing.md,
231
- },
232
- sectionHeader: {
233
- marginBottom: tokens.spacing.sm,
234
- paddingHorizontal: tokens.spacing.md,
235
- },
236
- });
237
- };