@umituz/react-native-settings 2.0.0 → 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.
- package/README.md +129 -3
- package/lib/__tests__/setup.d.ts +5 -0
- package/lib/__tests__/setup.d.ts.map +1 -0
- package/lib/__tests__/setup.js +143 -0
- package/lib/__tests__/setup.js.map +1 -0
- package/lib/domain/repositories/ISettingsRepository.d.ts +51 -0
- package/lib/domain/repositories/ISettingsRepository.d.ts.map +1 -0
- package/lib/domain/repositories/ISettingsRepository.js +8 -0
- package/lib/domain/repositories/ISettingsRepository.js.map +1 -0
- package/lib/index.d.ts +35 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +32 -0
- package/lib/index.js.map +1 -0
- package/lib/infrastructure/storage/SettingsStore.d.ts +36 -0
- package/lib/infrastructure/storage/SettingsStore.d.ts.map +1 -0
- package/lib/infrastructure/storage/SettingsStore.js +144 -0
- package/lib/infrastructure/storage/SettingsStore.js.map +1 -0
- package/lib/presentation/components/CloudSyncSetting.d.ts +16 -0
- package/lib/presentation/components/CloudSyncSetting.d.ts.map +1 -0
- package/lib/presentation/components/CloudSyncSetting.js +30 -0
- package/lib/presentation/components/CloudSyncSetting.js.map +1 -0
- package/lib/presentation/components/DisclaimerCard.d.ts +15 -0
- package/lib/presentation/components/DisclaimerCard.d.ts.map +1 -0
- package/lib/presentation/components/DisclaimerCard.js +73 -0
- package/lib/presentation/components/DisclaimerCard.js.map +1 -0
- package/lib/presentation/components/DisclaimerModal.d.ts +13 -0
- package/lib/presentation/components/DisclaimerModal.d.ts.map +1 -0
- package/lib/presentation/components/DisclaimerModal.js +62 -0
- package/lib/presentation/components/DisclaimerModal.js.map +1 -0
- package/lib/presentation/components/DisclaimerSetting.d.ts +39 -0
- package/lib/presentation/components/DisclaimerSetting.d.ts.map +1 -0
- package/lib/presentation/components/DisclaimerSetting.js +59 -0
- package/lib/presentation/components/DisclaimerSetting.js.map +1 -0
- package/lib/presentation/components/SettingItem.d.ts +45 -0
- package/lib/presentation/components/SettingItem.d.ts.map +1 -0
- package/lib/presentation/components/SettingItem.js +113 -0
- package/lib/presentation/components/SettingItem.js.map +1 -0
- package/lib/presentation/components/SettingsErrorBoundary.d.ts +23 -0
- package/lib/presentation/components/SettingsErrorBoundary.d.ts.map +1 -0
- package/lib/presentation/components/SettingsErrorBoundary.js +73 -0
- package/lib/presentation/components/SettingsErrorBoundary.js.map +1 -0
- package/lib/presentation/components/SettingsFooter.d.ts +11 -0
- package/lib/presentation/components/SettingsFooter.d.ts.map +1 -0
- package/lib/presentation/components/SettingsFooter.js +31 -0
- package/lib/presentation/components/SettingsFooter.js.map +1 -0
- package/lib/presentation/components/SettingsSection.d.ts +13 -0
- package/lib/presentation/components/SettingsSection.d.ts.map +1 -0
- package/lib/presentation/components/SettingsSection.js +37 -0
- package/lib/presentation/components/SettingsSection.js.map +1 -0
- package/lib/presentation/components/StorageClearSetting.d.ts +16 -0
- package/lib/presentation/components/StorageClearSetting.d.ts.map +1 -0
- package/lib/presentation/components/StorageClearSetting.js +21 -0
- package/lib/presentation/components/StorageClearSetting.js.map +1 -0
- package/lib/presentation/components/UserProfileHeader.d.ts +30 -0
- package/lib/presentation/components/UserProfileHeader.d.ts.map +1 -0
- package/lib/presentation/components/UserProfileHeader.js +119 -0
- package/lib/presentation/components/UserProfileHeader.js.map +1 -0
- package/lib/presentation/screens/AppearanceScreen.d.ts +8 -0
- package/lib/presentation/screens/AppearanceScreen.d.ts.map +1 -0
- package/lib/presentation/screens/AppearanceScreen.js +8 -0
- package/lib/presentation/screens/AppearanceScreen.js.map +1 -0
- package/lib/presentation/screens/SettingsScreen.d.ts +38 -0
- package/lib/presentation/screens/SettingsScreen.d.ts.map +1 -0
- package/lib/presentation/screens/SettingsScreen.js +37 -0
- package/lib/presentation/screens/SettingsScreen.js.map +1 -0
- package/lib/presentation/screens/components/AboutLegalSection.d.ts +15 -0
- package/lib/presentation/screens/components/AboutLegalSection.d.ts.map +1 -0
- package/lib/presentation/screens/components/AboutLegalSection.js +28 -0
- package/lib/presentation/screens/components/AboutLegalSection.js.map +1 -0
- package/lib/presentation/screens/components/AppearanceSection.d.ts +12 -0
- package/lib/presentation/screens/components/AppearanceSection.d.ts.map +1 -0
- package/lib/presentation/screens/components/AppearanceSection.js +21 -0
- package/lib/presentation/screens/components/AppearanceSection.js.map +1 -0
- package/lib/presentation/screens/components/LanguageSection.d.ts +12 -0
- package/lib/presentation/screens/components/LanguageSection.d.ts.map +1 -0
- package/lib/presentation/screens/components/LanguageSection.js +26 -0
- package/lib/presentation/screens/components/LanguageSection.js.map +1 -0
- package/lib/presentation/screens/components/NotificationsSection.d.ts +12 -0
- package/lib/presentation/screens/components/NotificationsSection.d.ts.map +1 -0
- package/lib/presentation/screens/components/NotificationsSection.js +58 -0
- package/lib/presentation/screens/components/NotificationsSection.js.map +1 -0
- package/lib/presentation/screens/components/SettingsContent.d.ts +36 -0
- package/lib/presentation/screens/components/SettingsContent.d.ts.map +1 -0
- package/lib/presentation/screens/components/SettingsContent.js +81 -0
- package/lib/presentation/screens/components/SettingsContent.js.map +1 -0
- package/lib/presentation/screens/components/SettingsHeader.d.ts +12 -0
- package/lib/presentation/screens/components/SettingsHeader.d.ts.map +1 -0
- package/lib/presentation/screens/components/SettingsHeader.js +59 -0
- package/lib/presentation/screens/components/SettingsHeader.js.map +1 -0
- package/lib/presentation/screens/components/index.d.ts +9 -0
- package/lib/presentation/screens/components/index.d.ts.map +1 -0
- package/lib/presentation/screens/components/index.js +9 -0
- package/lib/presentation/screens/components/index.js.map +1 -0
- package/lib/presentation/screens/hooks/useFeatureDetection.d.ts +21 -0
- package/lib/presentation/screens/hooks/useFeatureDetection.d.ts.map +1 -0
- package/lib/presentation/screens/hooks/useFeatureDetection.js +82 -0
- package/lib/presentation/screens/hooks/useFeatureDetection.js.map +1 -0
- package/lib/presentation/screens/types/CustomSection.d.ts +19 -0
- package/lib/presentation/screens/types/CustomSection.d.ts.map +1 -0
- package/lib/presentation/screens/types/CustomSection.js +6 -0
- package/lib/presentation/screens/types/CustomSection.js.map +1 -0
- package/lib/presentation/screens/types/ExtendedConfig.d.ts +68 -0
- package/lib/presentation/screens/types/ExtendedConfig.d.ts.map +1 -0
- package/lib/presentation/screens/types/ExtendedConfig.js +6 -0
- package/lib/presentation/screens/types/ExtendedConfig.js.map +1 -0
- package/lib/presentation/screens/types/FeatureConfig.d.ts +95 -0
- package/lib/presentation/screens/types/FeatureConfig.d.ts.map +1 -0
- package/lib/presentation/screens/types/FeatureConfig.js +6 -0
- package/lib/presentation/screens/types/FeatureConfig.js.map +1 -0
- package/lib/presentation/screens/types/SettingsConfig.d.ts +97 -0
- package/lib/presentation/screens/types/SettingsConfig.d.ts.map +1 -0
- package/lib/presentation/screens/types/SettingsConfig.js +6 -0
- package/lib/presentation/screens/types/SettingsConfig.js.map +1 -0
- package/lib/presentation/screens/types/index.d.ts +10 -0
- package/lib/presentation/screens/types/index.d.ts.map +1 -0
- package/lib/presentation/screens/types/index.js +6 -0
- package/lib/presentation/screens/types/index.js.map +1 -0
- package/lib/presentation/screens/utils/normalizeConfig.d.ts +44 -0
- package/lib/presentation/screens/utils/normalizeConfig.d.ts.map +1 -0
- package/lib/presentation/screens/utils/normalizeConfig.js +38 -0
- package/lib/presentation/screens/utils/normalizeConfig.js.map +1 -0
- package/package.json +46 -11
- package/src/__tests__/integration.test.tsx +371 -0
- package/src/__tests__/performance.test.tsx +369 -0
- package/src/__tests__/setup.test.tsx +20 -0
- package/src/__tests__/setup.ts +157 -0
- package/src/index.ts +9 -0
- package/src/infrastructure/storage/SettingsStore.ts +90 -45
- package/src/infrastructure/storage/__tests__/SettingsStore.test.tsx +302 -0
- package/src/presentation/components/CloudSyncSetting.tsx +11 -17
- package/src/presentation/components/DisclaimerCard.tsx +115 -0
- package/src/presentation/components/DisclaimerModal.tsx +104 -0
- package/src/presentation/components/DisclaimerSetting.tsx +77 -159
- package/src/presentation/components/SettingItem.tsx +11 -2
- package/src/presentation/components/SettingsErrorBoundary.tsx +126 -0
- package/src/presentation/components/StorageClearSetting.tsx +13 -8
- package/src/presentation/components/UserProfileHeader.tsx +48 -11
- package/src/presentation/components/__tests__/CloudSyncSetting.test.tsx +78 -0
- package/src/presentation/components/__tests__/DisclaimerCard.test.tsx +208 -0
- package/src/presentation/components/__tests__/DisclaimerModal.test.tsx +236 -0
- package/src/presentation/components/__tests__/DisclaimerSetting.test.tsx +74 -0
- package/src/presentation/components/__tests__/SettingItem.test.tsx +189 -0
- package/src/presentation/components/__tests__/SettingsErrorBoundary.test.tsx +186 -0
- package/src/presentation/screens/SettingsScreen.tsx +29 -159
- package/src/presentation/screens/__tests__/SettingsScreen.test.tsx +322 -0
- package/src/presentation/screens/components/AboutLegalSection.tsx +14 -5
- package/src/presentation/screens/components/AppearanceSection.tsx +1 -1
- package/src/presentation/screens/components/LanguageSection.tsx +2 -1
- package/src/presentation/screens/components/NotificationsSection.tsx +19 -14
- package/src/presentation/screens/components/SettingsContent.tsx +167 -0
- package/src/presentation/screens/components/SettingsHeader.tsx +79 -0
- package/src/presentation/screens/hooks/__tests__/useFeatureDetection.test.tsx +261 -0
- package/src/presentation/screens/hooks/useFeatureDetection.ts +15 -5
- package/src/presentation/screens/types/CustomSection.ts +20 -0
- package/src/presentation/screens/types/ExtendedConfig.ts +68 -0
- package/src/presentation/screens/types/FeatureConfig.ts +102 -0
- package/src/presentation/screens/types/SettingsConfig.ts +116 -0
- package/src/presentation/screens/types/index.ts +20 -0
- package/src/presentation/screens/utils/normalizeConfig.ts +2 -1
- 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
|
|
5
|
-
* Used in About screens for apps that require
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
export
|
|
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
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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={
|
|
102
|
+
onRequestClose={handleCloseModal}
|
|
88
103
|
>
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
+
});
|
|
@@ -18,13 +18,18 @@ export interface StorageClearSettingProps {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export const StorageClearSetting: React.FC<StorageClearSettingProps> = ({
|
|
21
|
-
title
|
|
22
|
-
description
|
|
21
|
+
title,
|
|
22
|
+
description,
|
|
23
23
|
onPress,
|
|
24
|
-
iconColor
|
|
25
|
-
titleColor
|
|
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={
|
|
37
|
-
value={
|
|
41
|
+
title={defaultTitle}
|
|
42
|
+
value={defaultDescription}
|
|
38
43
|
onPress={onPress}
|
|
39
|
-
iconColor={
|
|
40
|
-
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
},
|