@umituz/react-native-settings 4.17.14 → 4.17.16

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.
Files changed (103) hide show
  1. package/package.json +16 -15
  2. package/src/domains/about/__tests__/integration.test.tsx +328 -0
  3. package/src/domains/about/__tests__/types.d.ts +5 -0
  4. package/src/domains/about/domain/entities/AppInfo.ts +74 -0
  5. package/src/domains/about/domain/entities/__tests__/AppInfo.test.ts +93 -0
  6. package/src/domains/about/domain/repositories/IAboutRepository.ts +22 -0
  7. package/src/domains/about/index.ts +10 -0
  8. package/src/domains/about/infrastructure/repositories/AboutRepository.ts +68 -0
  9. package/src/domains/about/infrastructure/repositories/__tests__/AboutRepository.test.ts +153 -0
  10. package/src/domains/about/presentation/components/AboutContent.tsx +104 -0
  11. package/src/domains/about/presentation/components/AboutHeader.tsx +79 -0
  12. package/src/domains/about/presentation/components/AboutSection.tsx +134 -0
  13. package/src/domains/about/presentation/components/AboutSettingItem.tsx +208 -0
  14. package/src/domains/about/presentation/components/__tests__/AboutContent.simple.test.tsx +178 -0
  15. package/src/domains/about/presentation/components/__tests__/AboutContent.test.tsx +293 -0
  16. package/src/domains/about/presentation/components/__tests__/AboutHeader.test.tsx +201 -0
  17. package/src/domains/about/presentation/components/__tests__/AboutSettingItem.test.tsx +71 -0
  18. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.simple.test.tsx +229 -0
  19. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.test.tsx +240 -0
  20. package/src/domains/about/presentation/hooks/useAboutInfo.ts +262 -0
  21. package/src/domains/about/presentation/screens/AboutScreen.tsx +195 -0
  22. package/src/domains/about/presentation/screens/__tests__/AboutScreen.simple.test.tsx +199 -0
  23. package/src/domains/about/presentation/screens/__tests__/AboutScreen.test.tsx +366 -0
  24. package/src/domains/about/types/global.d.ts +15 -0
  25. package/src/domains/about/utils/__tests__/index.test.ts +408 -0
  26. package/src/domains/about/utils/index.ts +160 -0
  27. package/src/domains/appearance/__tests__/components/AppearanceScreen.test.tsx +195 -0
  28. package/src/domains/appearance/__tests__/hooks/index.test.tsx +232 -0
  29. package/src/domains/appearance/__tests__/integration/index.test.tsx +207 -0
  30. package/src/domains/appearance/__tests__/services/appearanceService.test.ts +299 -0
  31. package/src/domains/appearance/__tests__/setup.ts +96 -0
  32. package/src/domains/appearance/__tests__/stores/appearanceStore.test.tsx +175 -0
  33. package/src/domains/appearance/data/colorPalettes.ts +94 -0
  34. package/src/domains/appearance/hooks/index.ts +6 -0
  35. package/src/domains/appearance/hooks/useAppearance.ts +61 -0
  36. package/src/domains/appearance/hooks/useAppearanceActions.ts +144 -0
  37. package/src/domains/appearance/index.ts +7 -0
  38. package/src/domains/appearance/infrastructure/services/appearanceService.ts +301 -0
  39. package/src/domains/appearance/infrastructure/services/systemThemeDetection.ts +79 -0
  40. package/src/domains/appearance/infrastructure/services/validation.ts +91 -0
  41. package/src/domains/appearance/infrastructure/storage/appearanceStorage.ts +120 -0
  42. package/src/domains/appearance/infrastructure/stores/appearanceStore.ts +132 -0
  43. package/src/domains/appearance/presentation/components/AppearanceHeader.tsx +67 -0
  44. package/src/domains/appearance/presentation/components/AppearancePreview.tsx +141 -0
  45. package/src/domains/appearance/presentation/components/AppearanceSection.tsx +139 -0
  46. package/src/domains/appearance/presentation/components/ColorPicker.tsx +113 -0
  47. package/src/domains/appearance/presentation/components/CustomColorsSection.tsx +186 -0
  48. package/src/domains/appearance/presentation/components/ThemeModeSection.tsx +110 -0
  49. package/src/domains/appearance/presentation/components/ThemeOption.tsx +138 -0
  50. package/src/domains/appearance/presentation/components/index.ts +6 -0
  51. package/src/domains/appearance/presentation/screens/AppearanceScreen.tsx +226 -0
  52. package/src/domains/appearance/presentation/screens/index.ts +2 -0
  53. package/src/domains/appearance/types/index.ts +54 -0
  54. package/src/domains/faqs/domain/entities/FAQEntity.ts +16 -0
  55. package/src/domains/faqs/domain/services/FAQSearchService.ts +36 -0
  56. package/src/domains/faqs/domain/services/index.ts +1 -0
  57. package/src/domains/faqs/index.ts +7 -0
  58. package/src/domains/faqs/presentation/components/FAQCategory.tsx +71 -0
  59. package/src/domains/faqs/presentation/components/FAQEmptyState.tsx +75 -0
  60. package/src/domains/faqs/presentation/components/FAQItem.tsx +103 -0
  61. package/src/domains/faqs/presentation/components/FAQSearchBar.tsx +70 -0
  62. package/src/domains/faqs/presentation/components/FAQSection.tsx +50 -0
  63. package/src/domains/faqs/presentation/components/index.ts +18 -0
  64. package/src/domains/faqs/presentation/hooks/index.ts +6 -0
  65. package/src/domains/faqs/presentation/hooks/useFAQExpansion.ts +51 -0
  66. package/src/domains/faqs/presentation/hooks/useFAQSearch.ts +33 -0
  67. package/src/domains/faqs/presentation/screens/FAQScreen.tsx +129 -0
  68. package/src/domains/faqs/presentation/screens/index.ts +2 -0
  69. package/src/domains/feedback/domain/entities/FeedbackEntity.ts +92 -0
  70. package/src/domains/feedback/domain/repositories/IFeedbackRepository.ts +28 -0
  71. package/src/domains/feedback/index.ts +6 -0
  72. package/src/domains/feedback/presentation/components/FeedbackForm.tsx +189 -0
  73. package/src/domains/feedback/presentation/components/FeedbackModal.tsx +111 -0
  74. package/src/domains/feedback/presentation/components/SupportSection.tsx +160 -0
  75. package/src/domains/feedback/presentation/hooks/useDeleteFeedback.ts +25 -0
  76. package/src/domains/feedback/presentation/hooks/useFeedbackForm.ts +59 -0
  77. package/src/domains/feedback/presentation/hooks/useSubmitFeedback.ts +55 -0
  78. package/src/domains/feedback/presentation/hooks/useUserFeedback.ts +29 -0
  79. package/src/domains/legal/__tests__/ContentValidationService.test.ts +195 -0
  80. package/src/domains/legal/__tests__/StyleCacheService.test.ts +110 -0
  81. package/src/domains/legal/__tests__/UrlHandlerService.test.ts +71 -0
  82. package/src/domains/legal/__tests__/setup.ts +82 -0
  83. package/src/domains/legal/domain/entities/LegalConfig.ts +26 -0
  84. package/src/domains/legal/domain/services/ContentValidationService.ts +89 -0
  85. package/src/domains/legal/domain/services/StyleCacheService.ts +97 -0
  86. package/src/domains/legal/domain/services/UrlHandlerService.ts +128 -0
  87. package/src/domains/legal/index.ts +8 -0
  88. package/src/domains/legal/presentation/components/LegalItem.tsx +177 -0
  89. package/src/domains/legal/presentation/components/LegalLinks.tsx +154 -0
  90. package/src/domains/legal/presentation/components/LegalSection.tsx +134 -0
  91. package/src/domains/legal/presentation/screens/LegalScreen.tsx +237 -0
  92. package/src/domains/legal/presentation/screens/PrivacyPolicyScreen.tsx +214 -0
  93. package/src/domains/legal/presentation/screens/TermsOfServiceScreen.tsx +214 -0
  94. package/src/index.ts +19 -0
  95. package/src/presentation/components/DevSettingsSection.tsx +2 -2
  96. package/src/presentation/components/SettingItem.tsx +2 -2
  97. package/src/presentation/components/SettingsErrorBoundary.tsx +2 -2
  98. package/src/presentation/components/SettingsFooter.tsx +2 -2
  99. package/src/presentation/components/SettingsSection.tsx +2 -2
  100. package/src/presentation/navigation/SettingsStackNavigator.tsx +2 -2
  101. package/src/presentation/screens/SettingsScreen.tsx +2 -2
  102. package/src/presentation/screens/components/SettingsContent.tsx +2 -2
  103. package/src/presentation/screens/components/SettingsHeader.tsx +2 -2
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Tests for AboutRepository
3
+ */
4
+ import { AboutRepository } from '../AboutRepository';
5
+ import { AppInfo } from '../../../domain/entities/AppInfo';
6
+
7
+ describe('AboutRepository', () => {
8
+ let repository: AboutRepository;
9
+ const mockAppInfo: AppInfo = {
10
+ name: 'Test App',
11
+ version: '1.0.0',
12
+ description: 'Test Description',
13
+ developer: 'Test Developer',
14
+ };
15
+
16
+ beforeEach(() => {
17
+ repository = new AboutRepository();
18
+ });
19
+
20
+ afterEach(() => {
21
+ repository.destroy();
22
+ });
23
+
24
+ describe('saveAppInfo', () => {
25
+ it('should save app info successfully', async () => {
26
+ await expect(repository.saveAppInfo(mockAppInfo)).resolves.not.toThrow();
27
+
28
+ const savedInfo = await repository.getAppInfo();
29
+ expect(savedInfo).toEqual(mockAppInfo);
30
+ });
31
+
32
+ it('should throw error for invalid input', async () => {
33
+ await expect(repository.saveAppInfo(null as unknown)).rejects.toThrow('Invalid app info provided');
34
+ await expect(repository.saveAppInfo(undefined as unknown)).rejects.toThrow('Invalid app info provided');
35
+ await expect(repository.saveAppInfo('invalid' as unknown)).rejects.toThrow('Invalid app info provided');
36
+ });
37
+
38
+ it('should store a copy of the data', async () => {
39
+ const appInfoCopy = { ...mockAppInfo };
40
+ await repository.saveAppInfo(mockAppInfo);
41
+
42
+ // Modify original object
43
+ appInfoCopy.name = 'Modified';
44
+
45
+ const savedInfo = await repository.getAppInfo();
46
+ expect(savedInfo.name).toBe('Test App'); // Should not be affected
47
+ });
48
+ });
49
+
50
+ describe('getAppInfo', () => {
51
+ it('should return saved app info', async () => {
52
+ await repository.saveAppInfo(mockAppInfo);
53
+
54
+ const retrievedInfo = await repository.getAppInfo();
55
+ expect(retrievedInfo).toEqual(mockAppInfo);
56
+ });
57
+
58
+ it('should throw error when not initialized', async () => {
59
+ await expect(repository.getAppInfo()).rejects.toThrow('App info not initialized');
60
+ });
61
+
62
+ it('should return a copy of the data', async () => {
63
+ await repository.saveAppInfo(mockAppInfo);
64
+
65
+ const retrievedInfo = await repository.getAppInfo();
66
+
67
+ // Modify returned object
68
+ const modifiedInfo = { ...retrievedInfo };
69
+ modifiedInfo.name = 'Modified';
70
+
71
+ // Get it again to verify original is unchanged
72
+ const retrievedInfo2 = await repository.getAppInfo();
73
+ expect(retrievedInfo2.name).toBe('Test App');
74
+ });
75
+
76
+ it('should throw error when destroyed', async () => {
77
+ await repository.saveAppInfo(mockAppInfo);
78
+ repository.destroy();
79
+
80
+ await expect(repository.getAppInfo()).rejects.toThrow('Repository has been destroyed');
81
+ });
82
+ });
83
+
84
+ describe('updateAppInfo', () => {
85
+ beforeEach(async () => {
86
+ await repository.saveAppInfo(mockAppInfo);
87
+ });
88
+
89
+ it('should update app info successfully', async () => {
90
+ const updates = { name: 'Updated App', version: '2.0.0' };
91
+
92
+ const updatedInfo = await repository.updateAppInfo(updates);
93
+
94
+ expect(updatedInfo.name).toBe('Updated App');
95
+ expect(updatedInfo.version).toBe('2.0.0');
96
+ expect(updatedInfo.description).toBe('Test Description'); // Unchanged
97
+ expect(updatedInfo.developer).toBe('Test Developer'); // Unchanged
98
+ });
99
+
100
+ it('should throw error when not initialized', async () => {
101
+ const newRepository = new AboutRepository();
102
+
103
+ await expect(newRepository.updateAppInfo({ name: 'Test' })).rejects.toThrow('App info not initialized');
104
+ });
105
+
106
+ it('should throw error for invalid updates', async () => {
107
+ await expect(repository.updateAppInfo(null as unknown)).rejects.toThrow('Invalid updates provided');
108
+ await expect(repository.updateAppInfo(undefined as unknown)).rejects.toThrow('Invalid updates provided');
109
+ await expect(repository.updateAppInfo('invalid' as unknown)).rejects.toThrow('Invalid updates provided');
110
+ });
111
+
112
+ it('should return a copy of the updated data', async () => {
113
+ const updates = { name: 'Updated App' };
114
+
115
+ const updatedInfo = await repository.updateAppInfo(updates);
116
+
117
+ // Modify returned object
118
+ const modifiedInfo = { ...updatedInfo };
119
+ modifiedInfo.name = 'Modified';
120
+
121
+ // Get it again to verify original is unchanged
122
+ const updatedInfo2 = await repository.getAppInfo();
123
+ expect(updatedInfo2.name).toBe('Updated App');
124
+ });
125
+
126
+ it('should throw error when destroyed', async () => {
127
+ repository.destroy();
128
+
129
+ await expect(repository.updateAppInfo({ name: 'Test' })).rejects.toThrow('Repository has been destroyed');
130
+ });
131
+ });
132
+
133
+ describe('destroy', () => {
134
+ it('should cleanup resources', async () => {
135
+ await repository.saveAppInfo(mockAppInfo);
136
+
137
+ repository.destroy();
138
+
139
+ await expect(repository.getAppInfo()).rejects.toThrow('Repository has been destroyed');
140
+ await expect(repository.updateAppInfo({ name: 'Test' })).rejects.toThrow('Repository has been destroyed');
141
+ await expect(repository.saveAppInfo(mockAppInfo)).rejects.toThrow('Repository has been destroyed');
142
+ });
143
+
144
+ it('should be safe to call multiple times', async () => {
145
+ await repository.saveAppInfo(mockAppInfo);
146
+
147
+ repository.destroy();
148
+ repository.destroy(); // Should not throw
149
+
150
+ await expect(repository.getAppInfo()).rejects.toThrow('Repository has been destroyed');
151
+ });
152
+ });
153
+ });
@@ -0,0 +1,104 @@
1
+ /**
2
+ * About Content Component
3
+ * Displays the list of about items organized in sections
4
+ */
5
+ import React from 'react';
6
+ import { View, Text, StyleSheet } from 'react-native';
7
+ import { AboutSettingItem } from './AboutSettingItem';
8
+ import { AppInfo } from '../../domain/entities/AppInfo';
9
+ import { AboutConfig } from '../../domain/entities/AppInfo';
10
+
11
+ import { useResponsiveDesignTokens } from '@umituz/react-native-design-system';
12
+
13
+ export interface AboutContentProps {
14
+ /** App information to display */
15
+ appInfo: AppInfo;
16
+ /** Configuration for actions */
17
+ config: AboutConfig;
18
+ }
19
+
20
+ const AboutSectionHeader: React.FC<{ title: string }> = ({ title }) => {
21
+ const tokens = useResponsiveDesignTokens();
22
+ const colors = tokens.colors;
23
+
24
+ return (
25
+ <Text style={[styles.sectionHeader, { color: colors.textSecondary }]}>{title}</Text>
26
+ );
27
+ };
28
+
29
+ export const AboutContent: React.FC<AboutContentProps> = ({
30
+ appInfo,
31
+ config,
32
+ }) => {
33
+ const hasContactInfo = appInfo.developer || appInfo.contactEmail || appInfo.websiteUrl;
34
+ const hasMoreInfo = appInfo.moreAppsUrl;
35
+
36
+ const texts = config.texts || {};
37
+
38
+ return (
39
+ <View style={styles.content}>
40
+ {hasContactInfo && (
41
+ <View style={styles.section}>
42
+ <AboutSectionHeader title={texts.contact || "Contact"} />
43
+
44
+ {appInfo.developer && (
45
+ <AboutSettingItem
46
+ title={texts.developer || "Developer"}
47
+ value={appInfo.developer}
48
+ testID="developer-item"
49
+ />
50
+ )}
51
+
52
+ {appInfo.contactEmail && (
53
+ <AboutSettingItem
54
+ title={texts.email || "Email"}
55
+ value={appInfo.contactEmail}
56
+ onPress={config.actions?.onEmailPress}
57
+ testID="email-item"
58
+ showChevron={!!config.actions?.onEmailPress}
59
+ />
60
+ )}
61
+
62
+ {appInfo.websiteUrl && (
63
+ <AboutSettingItem
64
+ title={texts.website || "Website"}
65
+ value={appInfo.websiteDisplay || appInfo.websiteUrl}
66
+ onPress={config.actions?.onWebsitePress}
67
+ testID="website-item"
68
+ showChevron={!!config.actions?.onWebsitePress}
69
+ />
70
+ )}
71
+ </View>
72
+ )}
73
+
74
+ {hasMoreInfo && (
75
+ <View style={styles.section}>
76
+ <AboutSectionHeader title={texts.more || "More"} />
77
+ <AboutSettingItem
78
+ title={texts.moreApps || "More Apps"}
79
+ onPress={config.actions?.onMoreAppsPress}
80
+ testID="more-apps-item"
81
+ showChevron={!!config.actions?.onMoreAppsPress}
82
+ />
83
+ </View>
84
+ )}
85
+ </View>
86
+ );
87
+ };
88
+
89
+ const styles = StyleSheet.create({
90
+ content: {
91
+ paddingVertical: 8,
92
+ },
93
+ section: {
94
+ marginBottom: 24,
95
+ },
96
+ sectionHeader: {
97
+ fontSize: 13,
98
+ fontWeight: '600',
99
+ marginBottom: 8,
100
+ paddingHorizontal: 16,
101
+ textTransform: 'uppercase',
102
+ letterSpacing: 0.5,
103
+ },
104
+ });
@@ -0,0 +1,79 @@
1
+ /**
2
+ * About Header Component
3
+ * Displays app name, version, and description
4
+ */
5
+ import React from 'react';
6
+ import {
7
+ View,
8
+ Text,
9
+ StyleSheet,
10
+ ViewStyle,
11
+ TextStyle,
12
+ } from 'react-native';
13
+ import { AppInfo } from '../../domain/entities/AppInfo';
14
+
15
+ export interface AboutHeaderProps {
16
+ /** App information to display */
17
+ appInfo: AppInfo;
18
+ /** Custom container style */
19
+ containerStyle?: ViewStyle;
20
+ /** Custom title style */
21
+ titleStyle?: TextStyle;
22
+ /** Custom version style */
23
+ versionStyle?: TextStyle;
24
+ descriptionStyle?: TextStyle;
25
+ versionPrefix?: string;
26
+ testID?: string;
27
+ }
28
+
29
+ import { useResponsiveDesignTokens } from '@umituz/react-native-design-system';
30
+
31
+ export const AboutHeader: React.FC<AboutHeaderProps> = ({
32
+ appInfo,
33
+ containerStyle,
34
+ titleStyle,
35
+ versionStyle,
36
+ descriptionStyle,
37
+ versionPrefix = "Version",
38
+ testID,
39
+ }) => {
40
+ const tokens = useResponsiveDesignTokens();
41
+ const colors = tokens.colors;
42
+
43
+ return (
44
+ <View style={[styles.header, { borderBottomColor: colors.border }, containerStyle]} testID={testID}>
45
+ <Text style={[styles.appName, { color: colors.textPrimary }, titleStyle]}>{appInfo.name}</Text>
46
+ <Text style={[styles.version, { color: colors.textSecondary }, versionStyle]}>
47
+ {versionPrefix} {appInfo.version}
48
+ </Text>
49
+ {appInfo.description && (
50
+ <Text style={[styles.description, { color: colors.textSecondary }, descriptionStyle]}>
51
+ {appInfo.description}
52
+ </Text>
53
+ )}
54
+ </View>
55
+ );
56
+ };
57
+
58
+ const styles = StyleSheet.create({
59
+ header: {
60
+ alignItems: 'center',
61
+ paddingVertical: 24,
62
+ paddingHorizontal: 16,
63
+ borderBottomWidth: 1,
64
+ },
65
+ appName: {
66
+ fontSize: 24,
67
+ fontWeight: 'bold',
68
+ marginBottom: 4,
69
+ },
70
+ version: {
71
+ fontSize: 16,
72
+ marginBottom: 8,
73
+ },
74
+ description: {
75
+ fontSize: 14,
76
+ textAlign: 'center',
77
+ lineHeight: 20,
78
+ },
79
+ });
@@ -0,0 +1,134 @@
1
+ import React from 'react';
2
+ import { View, Pressable, StyleSheet, ViewStyle } from 'react-native';
3
+ import { useNavigation } from '@react-navigation/native';
4
+ import { useResponsiveDesignTokens, AtomicIcon, AtomicText } from '@umituz/react-native-design-system';
5
+ import { AboutConfig } from '../../domain/entities/AppInfo';
6
+
7
+ export interface AboutSectionProps {
8
+ config?: AboutConfig;
9
+ onPress?: () => void;
10
+ containerStyle?: ViewStyle;
11
+ title?: string;
12
+ description?: string;
13
+ sectionTitle?: string;
14
+ }
15
+
16
+ export const AboutSection: React.FC<AboutSectionProps> = ({
17
+ config,
18
+ onPress,
19
+ containerStyle,
20
+ title: propsTitle,
21
+ description: propsDescription,
22
+ sectionTitle: propsSectionTitle,
23
+ }) => {
24
+ const navigation = useNavigation();
25
+ const tokens = useResponsiveDesignTokens();
26
+ const colors = tokens.colors;
27
+
28
+ const route = config?.route || config?.defaultRoute || 'About';
29
+ const title = propsTitle || config?.title;
30
+ const description = propsDescription || config?.description;
31
+ const sectionTitle = propsSectionTitle || title;
32
+
33
+ const handlePress = () => {
34
+ if (onPress) {
35
+ onPress();
36
+ } else {
37
+ navigation.navigate(route as never);
38
+ }
39
+ };
40
+
41
+ if (!title) return null;
42
+
43
+ return (
44
+ <View style={[styles.sectionContainer, { backgroundColor: colors.surface }, containerStyle]}>
45
+ {!!sectionTitle && (
46
+ <View style={styles.headerContainer}>
47
+ <AtomicText
48
+ type="titleMedium"
49
+ color="primary"
50
+ >
51
+ {sectionTitle}
52
+ </AtomicText>
53
+ </View>
54
+ )}
55
+ <Pressable
56
+ style={({ pressed }) => [
57
+ styles.itemContainer,
58
+ {
59
+ backgroundColor: pressed ? `${colors.primary}08` : 'transparent',
60
+ },
61
+ ]}
62
+ onPress={handlePress}
63
+ >
64
+ <View style={styles.content}>
65
+ <View
66
+ style={[
67
+ styles.iconContainer,
68
+ { backgroundColor: `${colors.primary}15` },
69
+ ]}
70
+ >
71
+ <AtomicIcon name="information-circle" size="lg" color="primary" />
72
+ </View>
73
+ <View style={styles.textContainer}>
74
+ <AtomicText
75
+ type="bodyLarge"
76
+ color="primary"
77
+ numberOfLines={1}
78
+ style={{ marginBottom: 4 }}
79
+ >
80
+ {title}
81
+ </AtomicText>
82
+ {!!description && (
83
+ <AtomicText
84
+ type="bodyMedium"
85
+ color="secondary"
86
+ numberOfLines={2}
87
+ >
88
+ {description}
89
+ </AtomicText>
90
+ )}
91
+ </View>
92
+ <AtomicIcon name="chevron-right" size="md" color="secondary" />
93
+ </View>
94
+ </Pressable>
95
+ </View>
96
+ );
97
+ };
98
+
99
+ const styles = StyleSheet.create({
100
+ sectionContainer: {
101
+ marginBottom: 16,
102
+ borderRadius: 12,
103
+ overflow: 'hidden',
104
+ },
105
+ headerContainer: {
106
+ paddingHorizontal: 16,
107
+ paddingTop: 16,
108
+ paddingBottom: 8,
109
+ },
110
+ itemContainer: {
111
+ flexDirection: 'row',
112
+ alignItems: 'center',
113
+ paddingHorizontal: 16,
114
+ paddingVertical: 16,
115
+ minHeight: 72,
116
+ },
117
+ content: {
118
+ flex: 1,
119
+ flexDirection: 'row',
120
+ alignItems: 'center',
121
+ },
122
+ iconContainer: {
123
+ width: 48,
124
+ height: 48,
125
+ borderRadius: 12,
126
+ justifyContent: 'center',
127
+ alignItems: 'center',
128
+ marginRight: 16,
129
+ },
130
+ textContainer: {
131
+ flex: 1,
132
+ marginRight: 8,
133
+ },
134
+ });
@@ -0,0 +1,208 @@
1
+ /**
2
+ * About Setting Item Component
3
+ * Reusable setting item for about screen
4
+ * Fully configurable and generic
5
+ * Optimized for performance and memory safety
6
+ */
7
+ import React, { useMemo, useCallback } from 'react';
8
+ import {
9
+ TouchableOpacity,
10
+ View,
11
+ Text,
12
+ StyleSheet,
13
+ ViewStyle,
14
+ TextStyle,
15
+ } from 'react-native';
16
+
17
+ export interface AboutSettingItemProps {
18
+ /** Icon component (any React component) */
19
+ icon?: React.ReactNode;
20
+ /** Main title text */
21
+ title: string;
22
+ /** Optional description text */
23
+ description?: string;
24
+ /** Optional value to display on the right */
25
+ value?: string;
26
+ /** Callback when pressed */
27
+ onPress?: () => void;
28
+ /** Show chevron arrow on right */
29
+ showChevron?: boolean;
30
+ /** Custom container style */
31
+ containerStyle?: ViewStyle;
32
+ /** Custom title style */
33
+ titleStyle?: TextStyle;
34
+ /** Custom description style */
35
+ descriptionStyle?: TextStyle;
36
+ /** Custom value style */
37
+ valueStyle?: TextStyle;
38
+ /** Custom icon container style */
39
+ iconContainerStyle?: ViewStyle;
40
+ /** Make item look disabled */
41
+ disabled?: boolean;
42
+ /** Test ID for E2E testing */
43
+ testID?: string;
44
+ /** Custom chevron color */
45
+ chevronColor?: string;
46
+ }
47
+
48
+ import { useResponsiveDesignTokens } from '@umituz/react-native-design-system';
49
+
50
+ export const AboutSettingItem: React.FC<AboutSettingItemProps> = ({
51
+ icon,
52
+ title,
53
+ description,
54
+ value,
55
+ onPress,
56
+ showChevron = !!onPress,
57
+ containerStyle,
58
+ titleStyle,
59
+ descriptionStyle,
60
+ valueStyle,
61
+ iconContainerStyle,
62
+ disabled = false,
63
+ testID,
64
+ chevronColor,
65
+ }) => {
66
+ const tokens = useResponsiveDesignTokens();
67
+ const colors = tokens.colors;
68
+
69
+ // Memoize container type to prevent unnecessary re-renders
70
+ const Container = useMemo(() => {
71
+ return onPress ? TouchableOpacity : View;
72
+ }, [onPress]) as React.ComponentType<React.ComponentProps<typeof TouchableOpacity | typeof View>>;
73
+
74
+ // Memoize container styles
75
+ const containerStyles = useMemo(() => {
76
+ return [
77
+ styles.container,
78
+ { backgroundColor: colors.surface },
79
+ disabled && styles.disabled,
80
+ containerStyle
81
+ ];
82
+ }, [disabled, containerStyle, colors.surface]);
83
+
84
+ // Memoize icon container styles
85
+ const iconContainerStyles = useMemo(() => {
86
+ return [
87
+ styles.iconContainer,
88
+ iconContainerStyle
89
+ ];
90
+ }, [iconContainerStyle]);
91
+
92
+ // Memoize chevron styles
93
+ const chevronStyles = useMemo(() => {
94
+ return [
95
+ styles.chevron,
96
+ { color: chevronColor || colors.textSecondary }
97
+ ];
98
+ }, [chevronColor, colors.textSecondary]);
99
+
100
+ // Memoize press handler to prevent unnecessary re-renders
101
+ const handlePress = useCallback(() => {
102
+ if (onPress && !disabled) {
103
+ onPress();
104
+ }
105
+ }, [onPress, disabled]);
106
+
107
+ // Memoize icon rendering
108
+ const renderIcon = useMemo(() => {
109
+ if (!icon) {
110
+ return null;
111
+ }
112
+
113
+ return (
114
+ <View style={iconContainerStyles}>
115
+ {icon}
116
+ </View>
117
+ );
118
+ }, [icon, iconContainerStyles]);
119
+
120
+ // Memoize content rendering
121
+ const renderContent = useMemo(() => {
122
+ return (
123
+ <View style={styles.content}>
124
+ <Text style={[styles.title, { color: colors.textPrimary }, titleStyle]}>{title}</Text>
125
+ {description && (
126
+ <Text style={[styles.description, { color: colors.textSecondary }, descriptionStyle]}>
127
+ {description}
128
+ </Text>
129
+ )}
130
+ </View>
131
+ );
132
+ }, [title, description, titleStyle, descriptionStyle, colors.textPrimary, colors.textSecondary]);
133
+
134
+ // Memoize value rendering
135
+ const renderValue = useMemo(() => {
136
+ if (!value) {
137
+ return null;
138
+ }
139
+
140
+ return (
141
+ <Text style={[styles.value, { color: colors.textSecondary }, valueStyle]}>{value}</Text>
142
+ );
143
+ }, [value, valueStyle, colors.textSecondary]);
144
+
145
+ // Memoize chevron rendering
146
+ const renderChevron = useMemo(() => {
147
+ if (!showChevron) {
148
+ return null;
149
+ }
150
+
151
+ return (
152
+ <Text style={chevronStyles}>›</Text>
153
+ );
154
+ }, [showChevron, chevronStyles]);
155
+
156
+ return (
157
+ <Container
158
+ style={containerStyles}
159
+ onPress={handlePress}
160
+ disabled={disabled}
161
+ testID={testID}
162
+ >
163
+ {renderIcon}
164
+ {renderContent}
165
+ {renderValue}
166
+ {renderChevron}
167
+ </Container>
168
+ );
169
+ };
170
+
171
+ const styles = StyleSheet.create({
172
+ container: {
173
+ flexDirection: 'row',
174
+ alignItems: 'center',
175
+ paddingVertical: 12,
176
+ paddingHorizontal: 16,
177
+ },
178
+ disabled: {
179
+ opacity: 0.5,
180
+ },
181
+ iconContainer: {
182
+ marginRight: 12,
183
+ alignItems: 'center',
184
+ justifyContent: 'center',
185
+ },
186
+ content: {
187
+ flex: 1,
188
+ },
189
+ title: {
190
+ fontSize: 16,
191
+ fontWeight: '500',
192
+ },
193
+ description: {
194
+ fontSize: 14,
195
+ marginTop: 2,
196
+ },
197
+ value: {
198
+ fontSize: 16,
199
+ marginRight: 8,
200
+ flexShrink: 1,
201
+ textAlign: 'right',
202
+ maxWidth: '60%',
203
+ },
204
+ chevron: {
205
+ fontSize: 20,
206
+ fontWeight: '300',
207
+ },
208
+ });