@umituz/react-native-settings 1.11.4 → 2.4.0

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 (167) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +129 -3
  3. package/lib/__tests__/setup.d.ts +5 -0
  4. package/lib/__tests__/setup.d.ts.map +1 -0
  5. package/lib/__tests__/setup.js +143 -0
  6. package/lib/__tests__/setup.js.map +1 -0
  7. package/lib/domain/repositories/ISettingsRepository.d.ts +51 -0
  8. package/lib/domain/repositories/ISettingsRepository.d.ts.map +1 -0
  9. package/lib/domain/repositories/ISettingsRepository.js +8 -0
  10. package/lib/domain/repositories/ISettingsRepository.js.map +1 -0
  11. package/lib/index.d.ts +35 -0
  12. package/lib/index.d.ts.map +1 -0
  13. package/lib/index.js +32 -0
  14. package/lib/index.js.map +1 -0
  15. package/lib/infrastructure/storage/SettingsStore.d.ts +36 -0
  16. package/lib/infrastructure/storage/SettingsStore.d.ts.map +1 -0
  17. package/lib/infrastructure/storage/SettingsStore.js +144 -0
  18. package/lib/infrastructure/storage/SettingsStore.js.map +1 -0
  19. package/lib/presentation/components/CloudSyncSetting.d.ts +16 -0
  20. package/lib/presentation/components/CloudSyncSetting.d.ts.map +1 -0
  21. package/lib/presentation/components/CloudSyncSetting.js +30 -0
  22. package/lib/presentation/components/CloudSyncSetting.js.map +1 -0
  23. package/lib/presentation/components/DisclaimerCard.d.ts +15 -0
  24. package/lib/presentation/components/DisclaimerCard.d.ts.map +1 -0
  25. package/lib/presentation/components/DisclaimerCard.js +73 -0
  26. package/lib/presentation/components/DisclaimerCard.js.map +1 -0
  27. package/lib/presentation/components/DisclaimerModal.d.ts +13 -0
  28. package/lib/presentation/components/DisclaimerModal.d.ts.map +1 -0
  29. package/lib/presentation/components/DisclaimerModal.js +62 -0
  30. package/lib/presentation/components/DisclaimerModal.js.map +1 -0
  31. package/lib/presentation/components/DisclaimerSetting.d.ts +39 -0
  32. package/lib/presentation/components/DisclaimerSetting.d.ts.map +1 -0
  33. package/lib/presentation/components/DisclaimerSetting.js +59 -0
  34. package/lib/presentation/components/DisclaimerSetting.js.map +1 -0
  35. package/lib/presentation/components/SettingItem.d.ts +45 -0
  36. package/lib/presentation/components/SettingItem.d.ts.map +1 -0
  37. package/lib/presentation/components/SettingItem.js +113 -0
  38. package/lib/presentation/components/SettingItem.js.map +1 -0
  39. package/lib/presentation/components/SettingsErrorBoundary.d.ts +23 -0
  40. package/lib/presentation/components/SettingsErrorBoundary.d.ts.map +1 -0
  41. package/lib/presentation/components/SettingsErrorBoundary.js +73 -0
  42. package/lib/presentation/components/SettingsErrorBoundary.js.map +1 -0
  43. package/lib/presentation/components/SettingsFooter.d.ts +11 -0
  44. package/lib/presentation/components/SettingsFooter.d.ts.map +1 -0
  45. package/lib/presentation/components/SettingsFooter.js +31 -0
  46. package/lib/presentation/components/SettingsFooter.js.map +1 -0
  47. package/lib/presentation/components/SettingsSection.d.ts +13 -0
  48. package/lib/presentation/components/SettingsSection.d.ts.map +1 -0
  49. package/lib/presentation/components/SettingsSection.js +37 -0
  50. package/lib/presentation/components/SettingsSection.js.map +1 -0
  51. package/lib/presentation/components/StorageClearSetting.d.ts +16 -0
  52. package/lib/presentation/components/StorageClearSetting.d.ts.map +1 -0
  53. package/lib/presentation/components/StorageClearSetting.js +21 -0
  54. package/lib/presentation/components/StorageClearSetting.js.map +1 -0
  55. package/lib/presentation/components/UserProfileHeader.d.ts +30 -0
  56. package/lib/presentation/components/UserProfileHeader.d.ts.map +1 -0
  57. package/lib/presentation/components/UserProfileHeader.js +119 -0
  58. package/lib/presentation/components/UserProfileHeader.js.map +1 -0
  59. package/lib/presentation/screens/AppearanceScreen.d.ts +8 -0
  60. package/lib/presentation/screens/AppearanceScreen.d.ts.map +1 -0
  61. package/lib/presentation/screens/AppearanceScreen.js +8 -0
  62. package/lib/presentation/screens/AppearanceScreen.js.map +1 -0
  63. package/lib/presentation/screens/SettingsScreen.d.ts +38 -0
  64. package/lib/presentation/screens/SettingsScreen.d.ts.map +1 -0
  65. package/lib/presentation/screens/SettingsScreen.js +37 -0
  66. package/lib/presentation/screens/SettingsScreen.js.map +1 -0
  67. package/lib/presentation/screens/components/AboutLegalSection.d.ts +15 -0
  68. package/lib/presentation/screens/components/AboutLegalSection.d.ts.map +1 -0
  69. package/lib/presentation/screens/components/AboutLegalSection.js +28 -0
  70. package/lib/presentation/screens/components/AboutLegalSection.js.map +1 -0
  71. package/lib/presentation/screens/components/AppearanceSection.d.ts +12 -0
  72. package/lib/presentation/screens/components/AppearanceSection.d.ts.map +1 -0
  73. package/lib/presentation/screens/components/AppearanceSection.js +21 -0
  74. package/lib/presentation/screens/components/AppearanceSection.js.map +1 -0
  75. package/lib/presentation/screens/components/LanguageSection.d.ts +12 -0
  76. package/lib/presentation/screens/components/LanguageSection.d.ts.map +1 -0
  77. package/lib/presentation/screens/components/LanguageSection.js +26 -0
  78. package/lib/presentation/screens/components/LanguageSection.js.map +1 -0
  79. package/lib/presentation/screens/components/NotificationsSection.d.ts +12 -0
  80. package/lib/presentation/screens/components/NotificationsSection.d.ts.map +1 -0
  81. package/lib/presentation/screens/components/NotificationsSection.js +58 -0
  82. package/lib/presentation/screens/components/NotificationsSection.js.map +1 -0
  83. package/lib/presentation/screens/components/SettingsContent.d.ts +36 -0
  84. package/lib/presentation/screens/components/SettingsContent.d.ts.map +1 -0
  85. package/lib/presentation/screens/components/SettingsContent.js +81 -0
  86. package/lib/presentation/screens/components/SettingsContent.js.map +1 -0
  87. package/lib/presentation/screens/components/SettingsHeader.d.ts +12 -0
  88. package/lib/presentation/screens/components/SettingsHeader.d.ts.map +1 -0
  89. package/lib/presentation/screens/components/SettingsHeader.js +59 -0
  90. package/lib/presentation/screens/components/SettingsHeader.js.map +1 -0
  91. package/lib/presentation/screens/components/index.d.ts +9 -0
  92. package/lib/presentation/screens/components/index.d.ts.map +1 -0
  93. package/lib/presentation/screens/components/index.js +9 -0
  94. package/lib/presentation/screens/components/index.js.map +1 -0
  95. package/lib/presentation/screens/hooks/useFeatureDetection.d.ts +21 -0
  96. package/lib/presentation/screens/hooks/useFeatureDetection.d.ts.map +1 -0
  97. package/lib/presentation/screens/hooks/useFeatureDetection.js +82 -0
  98. package/lib/presentation/screens/hooks/useFeatureDetection.js.map +1 -0
  99. package/lib/presentation/screens/types/CustomSection.d.ts +19 -0
  100. package/lib/presentation/screens/types/CustomSection.d.ts.map +1 -0
  101. package/lib/presentation/screens/types/CustomSection.js +6 -0
  102. package/lib/presentation/screens/types/CustomSection.js.map +1 -0
  103. package/lib/presentation/screens/types/ExtendedConfig.d.ts +68 -0
  104. package/lib/presentation/screens/types/ExtendedConfig.d.ts.map +1 -0
  105. package/lib/presentation/screens/types/ExtendedConfig.js +6 -0
  106. package/lib/presentation/screens/types/ExtendedConfig.js.map +1 -0
  107. package/lib/presentation/screens/types/FeatureConfig.d.ts +95 -0
  108. package/lib/presentation/screens/types/FeatureConfig.d.ts.map +1 -0
  109. package/lib/presentation/screens/types/FeatureConfig.js +6 -0
  110. package/lib/presentation/screens/types/FeatureConfig.js.map +1 -0
  111. package/lib/presentation/screens/types/SettingsConfig.d.ts +97 -0
  112. package/lib/presentation/screens/types/SettingsConfig.d.ts.map +1 -0
  113. package/lib/presentation/screens/types/SettingsConfig.js +6 -0
  114. package/lib/presentation/screens/types/SettingsConfig.js.map +1 -0
  115. package/lib/presentation/screens/types/index.d.ts +10 -0
  116. package/lib/presentation/screens/types/index.d.ts.map +1 -0
  117. package/lib/presentation/screens/types/index.js +6 -0
  118. package/lib/presentation/screens/types/index.js.map +1 -0
  119. package/lib/presentation/screens/utils/normalizeConfig.d.ts +44 -0
  120. package/lib/presentation/screens/utils/normalizeConfig.d.ts.map +1 -0
  121. package/lib/presentation/screens/utils/normalizeConfig.js +38 -0
  122. package/lib/presentation/screens/utils/normalizeConfig.js.map +1 -0
  123. package/package.json +46 -11
  124. package/src/__tests__/integration.test.tsx +371 -0
  125. package/src/__tests__/performance.test.tsx +369 -0
  126. package/src/__tests__/setup.test.tsx +20 -0
  127. package/src/__tests__/setup.ts +157 -0
  128. package/src/domain/repositories/ISettingsRepository.ts +0 -0
  129. package/src/index.ts +9 -1
  130. package/src/infrastructure/storage/SettingsStore.ts +90 -45
  131. package/src/infrastructure/storage/__tests__/SettingsStore.test.tsx +302 -0
  132. package/src/presentation/components/CloudSyncSetting.tsx +11 -17
  133. package/src/presentation/components/DisclaimerCard.tsx +115 -0
  134. package/src/presentation/components/DisclaimerModal.tsx +104 -0
  135. package/src/presentation/components/DisclaimerSetting.tsx +77 -159
  136. package/src/presentation/components/SettingItem.tsx +11 -2
  137. package/src/presentation/components/SettingsErrorBoundary.tsx +126 -0
  138. package/src/presentation/components/SettingsFooter.tsx +0 -0
  139. package/src/presentation/components/SettingsSection.tsx +0 -0
  140. package/src/presentation/components/StorageClearSetting.tsx +13 -8
  141. package/src/presentation/components/UserProfileHeader.tsx +48 -11
  142. package/src/presentation/components/__tests__/CloudSyncSetting.test.tsx +78 -0
  143. package/src/presentation/components/__tests__/DisclaimerCard.test.tsx +208 -0
  144. package/src/presentation/components/__tests__/DisclaimerModal.test.tsx +236 -0
  145. package/src/presentation/components/__tests__/DisclaimerSetting.test.tsx +74 -0
  146. package/src/presentation/components/__tests__/SettingItem.test.tsx +189 -0
  147. package/src/presentation/components/__tests__/SettingsErrorBoundary.test.tsx +186 -0
  148. package/src/presentation/screens/AppearanceScreen.tsx +0 -0
  149. package/src/presentation/screens/SettingsScreen.tsx +29 -159
  150. package/src/presentation/screens/__tests__/SettingsScreen.test.tsx +322 -0
  151. package/src/presentation/screens/components/AboutLegalSection.tsx +14 -5
  152. package/src/presentation/screens/components/AppearanceSection.tsx +1 -1
  153. package/src/presentation/screens/components/LanguageSection.tsx +2 -1
  154. package/src/presentation/screens/components/NotificationsSection.tsx +19 -14
  155. package/src/presentation/screens/components/SettingsContent.tsx +167 -0
  156. package/src/presentation/screens/components/SettingsHeader.tsx +79 -0
  157. package/src/presentation/screens/components/index.ts +0 -0
  158. package/src/presentation/screens/hooks/__tests__/useFeatureDetection.test.tsx +261 -0
  159. package/src/presentation/screens/hooks/useFeatureDetection.ts +15 -5
  160. package/src/presentation/screens/types/CustomSection.ts +20 -0
  161. package/src/presentation/screens/types/ExtendedConfig.ts +68 -0
  162. package/src/presentation/screens/types/FeatureConfig.ts +102 -0
  163. package/src/presentation/screens/types/SettingsConfig.ts +116 -0
  164. package/src/presentation/screens/types/index.ts +20 -0
  165. package/src/presentation/screens/utils/normalizeConfig.ts +2 -1
  166. package/src/presentation/screens/LanguageSelectionScreen.tsx +0 -204
  167. package/src/presentation/screens/types.ts +0 -263
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Disclaimer Modal Component
3
+ * Extracted from DisclaimerSetting to follow single responsibility and 200-line rules
4
+ */
5
+
6
+ import React from 'react';
7
+ import {
8
+ View,
9
+ StyleSheet,
10
+ TouchableOpacity,
11
+ ScrollView,
12
+ } from 'react-native';
13
+
14
+ import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
15
+ import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system-atoms';
16
+
17
+ export interface DisclaimerModalProps {
18
+ visible: boolean;
19
+ title: string;
20
+ content: string;
21
+ onClose: () => void;
22
+ }
23
+
24
+ export const DisclaimerModal: React.FC<DisclaimerModalProps> = ({
25
+ visible,
26
+ title,
27
+ content,
28
+ onClose,
29
+ }) => {
30
+ const tokens = useAppDesignTokens();
31
+ const styles = getStyles(tokens);
32
+
33
+ if (!visible) return null;
34
+
35
+ return (
36
+ <View
37
+ style={[
38
+ styles.modalContainer,
39
+ { backgroundColor: tokens.colors.backgroundPrimary },
40
+ ]}
41
+ >
42
+ {/* Modal Header */}
43
+ <View
44
+ style={[
45
+ styles.modalHeader,
46
+ { borderBottomColor: tokens.colors.borderLight },
47
+ ]}
48
+ >
49
+ <AtomicText type="headlineMedium" color="primary">
50
+ {title}
51
+ </AtomicText>
52
+ <TouchableOpacity
53
+ onPress={onClose}
54
+ testID="close-disclaimer-modal"
55
+ >
56
+ <AtomicIcon name="X" color="primary" size="md" />
57
+ </TouchableOpacity>
58
+ </View>
59
+
60
+ {/* Scrollable Content */}
61
+ <ScrollView
62
+ style={styles.modalContent}
63
+ contentContainerStyle={styles.modalContentContainer}
64
+ >
65
+ <AtomicText
66
+ type="bodyMedium"
67
+ color="primary"
68
+ style={styles.modalText}
69
+ >
70
+ {content}
71
+ </AtomicText>
72
+ </ScrollView>
73
+ </View>
74
+ );
75
+ };
76
+
77
+ const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
78
+ StyleSheet.create({
79
+ modalContainer: {
80
+ flex: 1,
81
+ },
82
+
83
+ modalHeader: {
84
+ flexDirection: 'row',
85
+ justifyContent: 'space-between',
86
+ alignItems: 'center',
87
+ paddingHorizontal: 20,
88
+ paddingVertical: 16,
89
+ borderBottomWidth: 1,
90
+ },
91
+
92
+ modalContent: {
93
+ flex: 1,
94
+ },
95
+
96
+ modalContentContainer: {
97
+ padding: 20,
98
+ },
99
+
100
+ modalText: {
101
+ lineHeight: 24,
102
+ fontSize: 15,
103
+ },
104
+ });
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * DisclaimerSetting Component
3
3
  *
4
- * Displays health/wellness app disclaimer with important legal notice
5
- * Used in About screens for apps that require medical/health disclaimers
4
+ * Displays customizable disclaimer with important legal notice
5
+ * Used in About screens for apps that require disclaimers
6
6
  *
7
7
  * Features:
8
8
  * - Tappable card that opens full disclaimer modal
@@ -17,180 +17,98 @@
17
17
  * - Requires translations: settings.disclaimer.title, settings.disclaimer.message, settings.disclaimer.shortMessage
18
18
  */
19
19
 
20
- import React, { useState } from 'react';
21
- import {
22
- View,
23
- StyleSheet,
24
- TouchableOpacity,
25
- Modal,
26
- ScrollView,
27
- } from 'react-native';
20
+ import React, { useState, useEffect, useCallback } from 'react';
21
+ import { Modal } from 'react-native';
28
22
 
29
23
  import { useAppDesignTokens, withAlpha } from '@umituz/react-native-design-system-theme';
30
- import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system-atoms';
31
24
  import { useLocalization } from '@umituz/react-native-localization';
32
-
33
- type DesignTokens = ReturnType<typeof useAppDesignTokens>;
34
-
35
- export const DisclaimerSetting: React.FC = () => {
25
+ import { DisclaimerCard } from './DisclaimerCard';
26
+ import { DisclaimerModal } from './DisclaimerModal';
27
+
28
+ export interface DisclaimerSettingProps {
29
+ /** Custom title translation key */
30
+ titleKey?: string;
31
+ /** Custom message translation key */
32
+ messageKey?: string;
33
+ /** Custom short message translation key */
34
+ shortMessageKey?: string;
35
+ /** Custom icon name */
36
+ iconName?: string;
37
+ /** Custom icon color */
38
+ iconColor?: string;
39
+ /** Custom background color */
40
+ backgroundColor?: string;
41
+ /** Custom modal title */
42
+ modalTitle?: string;
43
+ /** Custom modal content */
44
+ modalContent?: string;
45
+ }
46
+
47
+ export const DisclaimerSetting: React.FC<DisclaimerSettingProps> = ({
48
+ titleKey = "settings.disclaimer.title",
49
+ messageKey = "settings.disclaimer.message",
50
+ shortMessageKey = "settings.disclaimer.shortMessage",
51
+ iconName = "AlertTriangle",
52
+ iconColor,
53
+ backgroundColor,
54
+ modalTitle,
55
+ modalContent,
56
+ }) => {
36
57
  const { t } = useLocalization();
37
58
  const tokens = useAppDesignTokens();
38
- const styles = getStyles(tokens);
39
59
  const [modalVisible, setModalVisible] = useState(false);
40
60
 
61
+ useEffect(() => {
62
+ return () => {
63
+ setModalVisible(false);
64
+ };
65
+ }, []);
66
+
67
+ const title = modalTitle || t(titleKey);
68
+ const content = modalContent || t(messageKey);
69
+ const shortMessage = t(shortMessageKey);
70
+ const finalIconColor = iconColor || tokens.colors.warning;
71
+ const finalBackgroundColor = backgroundColor || withAlpha(finalIconColor, 0.1);
72
+
73
+ const handleOpenModal = useCallback(() => {
74
+ setModalVisible(true);
75
+ if (__DEV__) {
76
+ console.log('DisclaimerSetting: Modal opened');
77
+ }
78
+ }, []);
79
+
80
+ const handleCloseModal = useCallback(() => {
81
+ setModalVisible(false);
82
+ if (__DEV__) {
83
+ console.log('DisclaimerSetting: Modal closed');
84
+ }
85
+ }, []);
86
+
41
87
  return (
42
88
  <>
43
- <TouchableOpacity
44
- style={[
45
- styles.container,
46
- { backgroundColor: withAlpha(tokens.colors.warning, 0.1) },
47
- ]}
48
- onPress={() => setModalVisible(true)}
49
- activeOpacity={0.7}
50
- testID="disclaimer-setting"
51
- >
52
- {/* Icon and Title Row */}
53
- <View style={styles.headerRow}>
54
- <View
55
- style={[
56
- styles.iconContainer,
57
- {
58
- backgroundColor: withAlpha(tokens.colors.warning, 0.2),
59
- borderColor: withAlpha(tokens.colors.warning, 0.4),
60
- borderWidth: 1,
61
- },
62
- ]}
63
- >
64
- <AtomicIcon name="AlertTriangle" color="warning" />
65
- </View>
66
- <AtomicText type="bodyLarge" color="primary" style={styles.title}>
67
- {t('settings.disclaimer.title')}
68
- </AtomicText>
69
- <AtomicIcon name="ArrowRight" color="secondary" size="sm" />
70
- </View>
89
+ <DisclaimerCard
90
+ title={title}
91
+ shortMessage={shortMessage}
92
+ iconName={iconName}
93
+ iconColor={finalIconColor}
94
+ backgroundColor={finalBackgroundColor}
95
+ onPress={handleOpenModal}
96
+ />
71
97
 
72
- {/* Short Message */}
73
- <AtomicText
74
- type="bodySmall"
75
- color="secondary"
76
- style={styles.shortMessage}
77
- >
78
- {t('settings.disclaimer.shortMessage')}
79
- </AtomicText>
80
- </TouchableOpacity>
81
-
82
- {/* Full Disclaimer Modal */}
83
98
  <Modal
84
99
  visible={modalVisible}
85
100
  animationType="slide"
86
101
  presentationStyle="pageSheet"
87
- onRequestClose={() => setModalVisible(false)}
102
+ onRequestClose={handleCloseModal}
88
103
  >
89
- <View
90
- style={[
91
- styles.modalContainer,
92
- { backgroundColor: tokens.colors.backgroundPrimary },
93
- ]}
94
- >
95
- {/* Modal Header */}
96
- <View
97
- style={[
98
- styles.modalHeader,
99
- { borderBottomColor: tokens.colors.borderLight },
100
- ]}
101
- >
102
- <AtomicText type="headlineMedium" color="primary">
103
- {t('settings.disclaimer.title')}
104
- </AtomicText>
105
- <TouchableOpacity
106
- onPress={() => setModalVisible(false)}
107
- testID="close-disclaimer-modal"
108
- >
109
- <AtomicIcon name="X" color="primary" size="md" />
110
- </TouchableOpacity>
111
- </View>
112
-
113
- {/* Scrollable Content */}
114
- <ScrollView
115
- style={styles.modalContent}
116
- contentContainerStyle={styles.modalContentContainer}
117
- >
118
- <AtomicText
119
- type="bodyMedium"
120
- color="primary"
121
- style={styles.modalText}
122
- >
123
- {t('settings.disclaimer.message')}
124
- </AtomicText>
125
- </ScrollView>
126
- </View>
104
+ <DisclaimerModal
105
+ visible={modalVisible}
106
+ title={title}
107
+ content={content}
108
+ onClose={handleCloseModal}
109
+ />
127
110
  </Modal>
128
111
  </>
129
112
  );
130
113
  };
131
114
 
132
- const getStyles = (tokens: DesignTokens) =>
133
- StyleSheet.create({
134
- container: {
135
- paddingHorizontal: tokens.spacing.md,
136
- paddingVertical: tokens.spacing.md,
137
- marginHorizontal: tokens.spacing.md,
138
- marginTop: 8,
139
- marginBottom: 8,
140
- borderRadius: 12,
141
- },
142
-
143
- headerRow: {
144
- flexDirection: 'row',
145
- alignItems: 'center',
146
- marginBottom: 12,
147
- },
148
-
149
- iconContainer: {
150
- width: 40,
151
- height: 40,
152
- borderRadius: 20,
153
- alignItems: 'center',
154
- justifyContent: 'center',
155
- marginRight: 12,
156
- },
157
-
158
- title: {
159
- flex: 1,
160
- fontWeight: tokens.typography.labelLarge.fontWeight as any,
161
- fontSize: tokens.typography.labelLarge.fontSize,
162
- },
163
-
164
- shortMessage: {
165
- lineHeight: 18,
166
- paddingLeft: 52, // Align with title (40px icon + 12px margin)
167
- fontSize: 13,
168
- },
169
-
170
- modalContainer: {
171
- flex: 1,
172
- },
173
-
174
- modalHeader: {
175
- flexDirection: 'row',
176
- justifyContent: 'space-between',
177
- alignItems: 'center',
178
- paddingHorizontal: 20,
179
- paddingVertical: 16,
180
- borderBottomWidth: 1,
181
- },
182
-
183
- modalContent: {
184
- flex: 1,
185
- },
186
-
187
- modalContentContainer: {
188
- padding: 20,
189
- },
190
-
191
- modalText: {
192
- lineHeight: 24,
193
- fontSize: 15,
194
- },
195
- });
196
-
@@ -34,6 +34,13 @@ export interface SettingItemProps {
34
34
  testID?: string;
35
35
  /** Disable the item */
36
36
  disabled?: boolean;
37
+ /** Custom switch thumb color */
38
+ switchThumbColor?: string;
39
+ /** Custom switch track colors */
40
+ switchTrackColors?: {
41
+ false: string;
42
+ true: string;
43
+ };
37
44
  }
38
45
 
39
46
  export const SettingItem: React.FC<SettingItemProps> = ({
@@ -49,6 +56,8 @@ export const SettingItem: React.FC<SettingItemProps> = ({
49
56
  titleColor,
50
57
  testID,
51
58
  disabled = false,
59
+ switchThumbColor,
60
+ switchTrackColors,
52
61
  }) => {
53
62
  const tokens = useAppDesignTokens();
54
63
  const colors = tokens.colors;
@@ -113,11 +122,11 @@ export const SettingItem: React.FC<SettingItemProps> = ({
113
122
  <Switch
114
123
  value={switchValue}
115
124
  onValueChange={onSwitchChange}
116
- trackColor={{
125
+ trackColor={switchTrackColors || {
117
126
  false: `${colors.textSecondary}30`,
118
127
  true: colors.primary,
119
128
  }}
120
- thumbColor="#FFFFFF"
129
+ thumbColor={switchThumbColor || "#FFFFFF"}
121
130
  ios_backgroundColor={`${colors.textSecondary}30`}
122
131
  />
123
132
  ) : (
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Settings Error Boundary Component
3
+ * Catches and handles errors in settings components
4
+ */
5
+
6
+ import React, { Component, ReactNode } from 'react';
7
+ import { View, StyleSheet } from 'react-native';
8
+ import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
9
+ import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system-atoms';
10
+
11
+ interface Props {
12
+ children: ReactNode;
13
+ fallback?: ReactNode;
14
+ fallbackTitle?: string;
15
+ fallbackMessage?: string;
16
+ }
17
+
18
+ interface State {
19
+ hasError: boolean;
20
+ error?: Error;
21
+ }
22
+
23
+ export class SettingsErrorBoundary extends Component<Props, State> {
24
+ constructor(props: Props) {
25
+ super(props);
26
+ this.state = { hasError: false };
27
+ }
28
+
29
+ static getDerivedStateFromError(error: Error): State {
30
+ return { hasError: true, error };
31
+ }
32
+
33
+ override componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
34
+ if (__DEV__) {
35
+ console.error('Settings Error Boundary caught an error:', error, errorInfo);
36
+ }
37
+ }
38
+
39
+ override render() {
40
+ if (this.state.hasError) {
41
+ if (this.props.fallback) {
42
+ return this.props.fallback;
43
+ }
44
+
45
+ return (
46
+ <ErrorBoundaryFallback
47
+ error={this.state.error}
48
+ fallbackTitle={this.props.fallbackTitle}
49
+ fallbackMessage={this.props.fallbackMessage}
50
+ />
51
+ );
52
+ }
53
+
54
+ return this.props.children;
55
+ }
56
+ }
57
+
58
+ interface ErrorBoundaryFallbackProps {
59
+ error?: Error;
60
+ fallbackTitle?: string;
61
+ fallbackMessage?: string;
62
+ }
63
+
64
+ const ErrorBoundaryFallback: React.FC<ErrorBoundaryFallbackProps> = ({
65
+ error,
66
+ fallbackTitle = "error_boundary.title",
67
+ fallbackMessage = "error_boundary.message"
68
+ }) => {
69
+ const tokens = useAppDesignTokens();
70
+
71
+ const title = __DEV__ && error?.message ? "error_boundary.dev_title" : fallbackTitle;
72
+ const message = __DEV__ && error?.message
73
+ ? `error_boundary.dev_message: ${error.message}`
74
+ : fallbackMessage;
75
+
76
+ return (
77
+ <View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
78
+ <View style={[styles.content, { backgroundColor: tokens.colors.surface }]}>
79
+ <AtomicIcon
80
+ name="AlertTriangle"
81
+ color="warning"
82
+ size="lg"
83
+ style={styles.icon}
84
+ />
85
+ <AtomicText
86
+ type="headlineSmall"
87
+ color="primary"
88
+ style={styles.title}
89
+ >
90
+ {title}
91
+ </AtomicText>
92
+ <AtomicText
93
+ type="bodyMedium"
94
+ color="secondary"
95
+ style={styles.message}
96
+ >
97
+ {message}
98
+ </AtomicText>
99
+ </View>
100
+ </View>
101
+ );
102
+ };
103
+
104
+ const styles = StyleSheet.create({
105
+ container: {
106
+ flex: 1,
107
+ padding: 16,
108
+ justifyContent: 'center',
109
+ },
110
+ content: {
111
+ alignItems: 'center',
112
+ padding: 24,
113
+ borderRadius: 12,
114
+ },
115
+ icon: {
116
+ marginBottom: 16,
117
+ },
118
+ title: {
119
+ marginBottom: 8,
120
+ textAlign: 'center',
121
+ },
122
+ message: {
123
+ textAlign: 'center',
124
+ lineHeight: 20,
125
+ },
126
+ });
File without changes
File without changes
@@ -18,13 +18,18 @@ export interface StorageClearSettingProps {
18
18
  }
19
19
 
20
20
  export const StorageClearSetting: React.FC<StorageClearSettingProps> = ({
21
- title = "Clear All Storage",
22
- description = "Clear all local storage data (DEV only)",
21
+ title,
22
+ description,
23
23
  onPress,
24
- iconColor = "#EF4444",
25
- titleColor = "#EF4444",
24
+ iconColor,
25
+ titleColor,
26
26
  isLast = false,
27
27
  }) => {
28
+ // Default values for DEV mode
29
+ const defaultTitle = title || "Clear All Storage";
30
+ const defaultDescription = description || "Clear all local storage data (DEV only)";
31
+ const defaultIconColor = iconColor || "#EF4444";
32
+ const defaultTitleColor = titleColor || "#EF4444";
28
33
  // Only render in DEV mode
29
34
  if (!__DEV__) {
30
35
  return null;
@@ -33,11 +38,11 @@ export const StorageClearSetting: React.FC<StorageClearSettingProps> = ({
33
38
  return (
34
39
  <SettingItem
35
40
  icon={Trash2}
36
- title={title}
37
- value={description}
41
+ title={defaultTitle}
42
+ value={defaultDescription}
38
43
  onPress={onPress}
39
- iconColor={iconColor}
40
- titleColor={titleColor}
44
+ iconColor={defaultIconColor}
45
+ titleColor={defaultTitleColor}
41
46
  isLast={isLast}
42
47
  />
43
48
  );
@@ -4,7 +4,7 @@
4
4
  * Works for both guest and authenticated users
5
5
  */
6
6
 
7
- import React from "react";
7
+ import React, { useState, useCallback } from "react";
8
8
  import { View, TouchableOpacity, StyleSheet, Image } from "react-native";
9
9
  import { ChevronRight } from "lucide-react-native";
10
10
  import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
@@ -24,6 +24,14 @@ export interface UserProfileHeaderProps {
24
24
  accountSettingsRoute?: string;
25
25
  /** Custom onPress handler */
26
26
  onPress?: () => void;
27
+ /** Custom guest user display name */
28
+ guestDisplayName?: string;
29
+ /** Custom avatar service URL */
30
+ avatarServiceUrl?: string;
31
+ /** Default user display name when no displayName provided */
32
+ defaultUserDisplayName?: string;
33
+ /** Default guest display name */
34
+ defaultGuestDisplayName?: string;
27
35
  }
28
36
 
29
37
  export const UserProfileHeader: React.FC<UserProfileHeaderProps> = ({
@@ -33,25 +41,32 @@ export const UserProfileHeader: React.FC<UserProfileHeaderProps> = ({
33
41
  avatarUrl,
34
42
  accountSettingsRoute,
35
43
  onPress,
44
+ guestDisplayName,
45
+ avatarServiceUrl,
46
+ defaultUserDisplayName,
47
+ defaultGuestDisplayName,
36
48
  }) => {
37
49
  const tokens = useAppDesignTokens();
38
50
  const navigation = useNavigation();
39
51
  const colors = tokens.colors;
40
52
  const spacing = tokens.spacing;
53
+ const [imageError, setImageError] = useState(false);
41
54
 
42
- const finalDisplayName = displayName || (isGuest ? "Guest" : "User");
43
- const avatarName = isGuest ? "Guest" : finalDisplayName;
55
+ const finalDisplayName = displayName || (isGuest ? guestDisplayName || defaultGuestDisplayName || "Guest" : defaultUserDisplayName || "User");
56
+ const avatarName = isGuest ? guestDisplayName || defaultGuestDisplayName || defaultGuestDisplayName || "Guest" : finalDisplayName;
57
+
58
+ const defaultAvatarService = avatarServiceUrl || "https://ui-avatars.com/api";
44
59
  const finalAvatarUrl =
45
- avatarUrl ||
46
- `https://ui-avatars.com/api/?name=${encodeURIComponent(avatarName)}&background=${colors.primary.replace("#", "")}&color=fff&size=64`;
60
+ (imageError ? null : avatarUrl) ||
61
+ `${defaultAvatarService}/?name=${encodeURIComponent(avatarName)}&background=${colors.primary.replace("#", "")}&color=fff&size=64`;
47
62
 
48
- const handlePress = () => {
63
+ const handlePress = useCallback(() => {
49
64
  if (onPress) {
50
65
  onPress();
51
66
  } else if (accountSettingsRoute) {
52
67
  navigation.navigate(accountSettingsRoute as never);
53
68
  }
54
- };
69
+ }, [onPress, accountSettingsRoute, navigation]);
55
70
 
56
71
  const shouldShowChevron = !!(onPress || accountSettingsRoute);
57
72
  const isPressable = !!(onPress || accountSettingsRoute);
@@ -70,10 +85,23 @@ export const UserProfileHeader: React.FC<UserProfileHeaderProps> = ({
70
85
  <>
71
86
  <View style={styles.content}>
72
87
  <View style={[styles.avatarContainer, { borderColor: `${colors.primary}30` }]}>
73
- <Image
74
- source={{ uri: finalAvatarUrl }}
75
- style={styles.avatar}
76
- />
88
+ {finalAvatarUrl ? (
89
+ <Image
90
+ source={{ uri: finalAvatarUrl }}
91
+ style={styles.avatar}
92
+ onError={() => setImageError(true)}
93
+ />
94
+ ) : (
95
+ <View style={[styles.avatarFallback, { backgroundColor: `${colors.primary}20` }]}>
96
+ <AtomicText
97
+ type="headlineMedium"
98
+ color="primary"
99
+ style={styles.avatarText}
100
+ >
101
+ {avatarName.charAt(0).toUpperCase()}
102
+ </AtomicText>
103
+ </View>
104
+ )}
77
105
  </View>
78
106
  <View style={[styles.textContainer, { marginLeft: spacing.md }]}>
79
107
  <AtomicText
@@ -135,6 +163,15 @@ const styles = StyleSheet.create({
135
163
  width: "100%",
136
164
  height: "100%",
137
165
  },
166
+ avatarFallback: {
167
+ width: "100%",
168
+ height: "100%",
169
+ justifyContent: "center",
170
+ alignItems: "center",
171
+ },
172
+ avatarText: {
173
+ fontWeight: "700",
174
+ },
138
175
  textContainer: {
139
176
  flex: 1,
140
177
  },