@umituz/react-native-settings 5.4.9 → 5.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/base/BaseService.ts +141 -0
- package/src/core/index.ts +60 -0
- package/src/core/patterns/Modal/ModalConfig.ts +282 -0
- package/src/core/patterns/Modal/useModalState.ts +128 -0
- package/src/core/patterns/Screen/ScreenConfig.ts +375 -0
- package/src/core/patterns/Screen/useScreenData.ts +201 -0
- package/src/core/utils/logger.ts +138 -0
- package/src/core/utils/validators.ts +203 -0
- package/src/domains/disclaimer/index.ts +0 -3
- package/src/domains/disclaimer/presentation/components/DisclaimerSetting.tsx +18 -43
- package/src/domains/disclaimer/presentation/screens/DisclaimerScreen.tsx +42 -92
- package/src/domains/feedback/index.ts +2 -1
- package/src/domains/feedback/presentation/components/SupportSection.tsx +16 -43
- package/src/domains/feedback/presentation/screens/FeatureRequestScreen.tsx +4 -4
- package/src/domains/feedback/presentation/screens/FeedbackScreen.tsx +75 -0
- package/src/domains/notifications/infrastructure/services/NotificationService.ts +16 -13
- package/src/domains/rating/application/services/RatingService.ts +115 -79
- package/src/domains/rating/index.ts +3 -3
- package/src/domains/rating/presentation/hooks/useAppRating.tsx +42 -65
- package/src/domains/rating/presentation/screens/RatingPromptScreen.tsx +162 -0
- package/src/index.ts +12 -0
- package/src/infrastructure/services/SettingsService.ts +23 -19
- package/src/presentation/components/GenericModal.tsx +208 -0
- package/src/presentation/components/GenericScreen.tsx +273 -0
- package/src/presentation/components/index.ts +27 -0
- package/src/presentation/navigation/hooks/useSettingsScreens.ts +26 -1
- package/src/presentation/navigation/types.ts +6 -0
- package/src/domains/disclaimer/presentation/components/DisclaimerModal.tsx +0 -103
- package/src/domains/feedback/presentation/components/FeedbackModal.tsx +0 -99
- package/src/domains/rating/presentation/components/RatingPromptModal.tsx +0 -152
|
@@ -1,103 +0,0 @@
|
|
|
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 { AtomicText, AtomicIcon } from '@umituz/react-native-design-system/atoms';
|
|
15
|
-
import { BaseModal } from '@umituz/react-native-design-system/molecules';
|
|
16
|
-
import { useAppDesignTokens } from '@umituz/react-native-design-system/theme';
|
|
17
|
-
|
|
18
|
-
export interface DisclaimerModalProps {
|
|
19
|
-
visible: boolean;
|
|
20
|
-
title: string;
|
|
21
|
-
content: string;
|
|
22
|
-
onClose: () => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const DisclaimerModal: React.FC<DisclaimerModalProps> = ({
|
|
26
|
-
visible,
|
|
27
|
-
title,
|
|
28
|
-
content,
|
|
29
|
-
onClose,
|
|
30
|
-
}) => {
|
|
31
|
-
const tokens = useAppDesignTokens();
|
|
32
|
-
const styles = getStyles(tokens);
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<BaseModal visible={visible} onClose={onClose}>
|
|
36
|
-
<View style={styles.modalContentWrapper}>
|
|
37
|
-
{/* Modal Header */}
|
|
38
|
-
<View
|
|
39
|
-
style={[
|
|
40
|
-
styles.modalHeader,
|
|
41
|
-
{ borderBottomColor: tokens.colors.border },
|
|
42
|
-
]}
|
|
43
|
-
>
|
|
44
|
-
<AtomicText type="headlineMedium" color="primary">
|
|
45
|
-
{title}
|
|
46
|
-
</AtomicText>
|
|
47
|
-
<TouchableOpacity
|
|
48
|
-
onPress={onClose}
|
|
49
|
-
testID="close-disclaimer-modal"
|
|
50
|
-
>
|
|
51
|
-
<AtomicIcon name="close" color="primary" size="md" />
|
|
52
|
-
</TouchableOpacity>
|
|
53
|
-
</View>
|
|
54
|
-
|
|
55
|
-
{/* Scrollable Content */}
|
|
56
|
-
<ScrollView
|
|
57
|
-
style={styles.modalContent}
|
|
58
|
-
contentContainerStyle={styles.modalContentContainer}
|
|
59
|
-
>
|
|
60
|
-
<AtomicText
|
|
61
|
-
type="bodyMedium"
|
|
62
|
-
color="textPrimary"
|
|
63
|
-
style={styles.modalText}
|
|
64
|
-
>
|
|
65
|
-
{content}
|
|
66
|
-
</AtomicText>
|
|
67
|
-
</ScrollView>
|
|
68
|
-
</View>
|
|
69
|
-
</BaseModal>
|
|
70
|
-
);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const getStyles = (_tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
75
|
-
StyleSheet.create({
|
|
76
|
-
modalContainer: {
|
|
77
|
-
flex: 1,
|
|
78
|
-
},
|
|
79
|
-
modalContentWrapper: {
|
|
80
|
-
flex: 1,
|
|
81
|
-
},
|
|
82
|
-
modalHeader: {
|
|
83
|
-
flexDirection: 'row',
|
|
84
|
-
justifyContent: 'space-between',
|
|
85
|
-
alignItems: 'center',
|
|
86
|
-
paddingHorizontal: 20,
|
|
87
|
-
paddingVertical: 16,
|
|
88
|
-
borderBottomWidth: 1,
|
|
89
|
-
},
|
|
90
|
-
|
|
91
|
-
modalContent: {
|
|
92
|
-
flex: 1,
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
modalContentContainer: {
|
|
96
|
-
padding: 20,
|
|
97
|
-
},
|
|
98
|
-
|
|
99
|
-
modalText: {
|
|
100
|
-
lineHeight: 24,
|
|
101
|
-
fontSize: 15, // or tokens.typography.bodyMedium.responsiveFontSize
|
|
102
|
-
},
|
|
103
|
-
});
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Feedback Modal Component
|
|
3
|
-
* Modal wrapper for providing feedback
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { View, StyleSheet, TouchableOpacity } from "react-native";
|
|
8
|
-
import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
|
|
9
|
-
import { ScreenLayout } from "@umituz/react-native-design-system/layouts";
|
|
10
|
-
import { BaseModal } from "@umituz/react-native-design-system/molecules";
|
|
11
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
12
|
-
import { FeedbackForm } from "./FeedbackForm";
|
|
13
|
-
import type { FeedbackType, FeedbackRating } from "../../domain/entities/FeedbackEntity";
|
|
14
|
-
import type { FeedbackFormProps } from "./FeedbackFormProps";
|
|
15
|
-
|
|
16
|
-
export interface FeedbackModalProps {
|
|
17
|
-
visible: boolean;
|
|
18
|
-
onClose: () => void;
|
|
19
|
-
onSubmit: (data: { type: FeedbackType; rating: FeedbackRating; description: string; title: string }) => Promise<void>;
|
|
20
|
-
initialType?: FeedbackType;
|
|
21
|
-
isSubmitting?: boolean;
|
|
22
|
-
title?: string;
|
|
23
|
-
subtitle?: string;
|
|
24
|
-
texts: FeedbackFormProps['texts'];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const FeedbackModal: React.FC<FeedbackModalProps> = ({
|
|
28
|
-
visible,
|
|
29
|
-
onClose,
|
|
30
|
-
onSubmit,
|
|
31
|
-
initialType,
|
|
32
|
-
isSubmitting,
|
|
33
|
-
title,
|
|
34
|
-
subtitle,
|
|
35
|
-
texts,
|
|
36
|
-
}) => {
|
|
37
|
-
const tokens = useAppDesignTokens();
|
|
38
|
-
const styles = getStyles(tokens);
|
|
39
|
-
|
|
40
|
-
const header = (
|
|
41
|
-
<View style={styles.header}>
|
|
42
|
-
<View style={styles.headerText}>
|
|
43
|
-
{title && <AtomicText style={{ fontSize: 20, fontWeight: '600' }}>{title}</AtomicText>}
|
|
44
|
-
{subtitle && <AtomicText style={{ fontSize: 14, marginTop: 4 }}>{subtitle}</AtomicText>}
|
|
45
|
-
</View>
|
|
46
|
-
<TouchableOpacity
|
|
47
|
-
onPress={onClose}
|
|
48
|
-
style={[styles.closeButton, { backgroundColor: tokens.colors.surfaceVariant }]}
|
|
49
|
-
>
|
|
50
|
-
<AtomicIcon name="close" size="sm" color="onSurface" />
|
|
51
|
-
</TouchableOpacity>
|
|
52
|
-
</View>
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<BaseModal visible={visible} onClose={onClose}>
|
|
57
|
-
<ScreenLayout
|
|
58
|
-
header={header}
|
|
59
|
-
scrollable={true}
|
|
60
|
-
edges={[]}
|
|
61
|
-
keyboardAvoiding={true}
|
|
62
|
-
contentContainerStyle={styles.content}
|
|
63
|
-
hideScrollIndicator={false}
|
|
64
|
-
>
|
|
65
|
-
<FeedbackForm
|
|
66
|
-
onSubmit={onSubmit}
|
|
67
|
-
initialType={initialType}
|
|
68
|
-
isSubmitting={isSubmitting}
|
|
69
|
-
texts={texts}
|
|
70
|
-
/>
|
|
71
|
-
</ScreenLayout>
|
|
72
|
-
</BaseModal>
|
|
73
|
-
);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const getStyles = (_tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
78
|
-
StyleSheet.create({
|
|
79
|
-
header: {
|
|
80
|
-
flexDirection: "row",
|
|
81
|
-
justifyContent: "space-between",
|
|
82
|
-
alignItems: "center",
|
|
83
|
-
padding: 16,
|
|
84
|
-
borderBottomWidth: 1,
|
|
85
|
-
},
|
|
86
|
-
headerText: {
|
|
87
|
-
flex: 1,
|
|
88
|
-
},
|
|
89
|
-
closeButton: {
|
|
90
|
-
width: 36,
|
|
91
|
-
height: 36,
|
|
92
|
-
borderRadius: 18,
|
|
93
|
-
justifyContent: "center",
|
|
94
|
-
alignItems: "center",
|
|
95
|
-
},
|
|
96
|
-
content: {
|
|
97
|
-
padding: 20,
|
|
98
|
-
},
|
|
99
|
-
});
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rating Prompt Modal
|
|
3
|
-
* 2-step rating prompt: Custom modal → Native review prompt
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { Modal, View, StyleSheet } from "react-native";
|
|
8
|
-
import { AtomicText, AtomicButton, AtomicIcon } from "@umituz/react-native-design-system/atoms";
|
|
9
|
-
import { useResponsive } from "@umituz/react-native-design-system/responsive";
|
|
10
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
11
|
-
import type { RatingTranslations } from "../../domain/entities/RatingConfig";
|
|
12
|
-
|
|
13
|
-
export interface RatingPromptModalProps {
|
|
14
|
-
visible: boolean;
|
|
15
|
-
onPositive: () => void;
|
|
16
|
-
onNegative: () => void;
|
|
17
|
-
onLater: () => void;
|
|
18
|
-
onDismiss: () => void;
|
|
19
|
-
translations?: RatingTranslations;
|
|
20
|
-
appName?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const RatingPromptModal: React.FC<RatingPromptModalProps> = ({
|
|
24
|
-
visible,
|
|
25
|
-
onPositive,
|
|
26
|
-
onNegative,
|
|
27
|
-
onLater,
|
|
28
|
-
onDismiss,
|
|
29
|
-
translations,
|
|
30
|
-
appName = "this app",
|
|
31
|
-
}) => {
|
|
32
|
-
const tokens = useAppDesignTokens();
|
|
33
|
-
const responsive = useResponsive();
|
|
34
|
-
|
|
35
|
-
const defaultTranslations: RatingTranslations = {
|
|
36
|
-
title: translations?.title ?? "Enjoying the app?",
|
|
37
|
-
message:
|
|
38
|
-
translations?.message ??
|
|
39
|
-
`If you love using ${appName}, would you mind taking a moment to rate it?`,
|
|
40
|
-
positiveButton: translations?.positiveButton ?? "Yes, I love it!",
|
|
41
|
-
negativeButton: translations?.negativeButton ?? "Not really",
|
|
42
|
-
laterButton: translations?.laterButton ?? "Maybe later",
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<Modal
|
|
47
|
-
visible={visible}
|
|
48
|
-
transparent
|
|
49
|
-
animationType="none"
|
|
50
|
-
onRequestClose={onDismiss}
|
|
51
|
-
statusBarTranslucent
|
|
52
|
-
>
|
|
53
|
-
<View
|
|
54
|
-
style={[
|
|
55
|
-
styles.overlay,
|
|
56
|
-
{ backgroundColor: "rgba(0, 0, 0, 0.5)" },
|
|
57
|
-
]}
|
|
58
|
-
>
|
|
59
|
-
<View
|
|
60
|
-
style={[
|
|
61
|
-
styles.container,
|
|
62
|
-
{
|
|
63
|
-
backgroundColor: tokens.colors.surface,
|
|
64
|
-
borderRadius: tokens.borders.radius.xl,
|
|
65
|
-
padding: tokens.spacing.lg,
|
|
66
|
-
maxWidth: responsive.maxContentWidth * 0.85,
|
|
67
|
-
width: "90%",
|
|
68
|
-
},
|
|
69
|
-
]}
|
|
70
|
-
>
|
|
71
|
-
<View style={styles.iconContainer}>
|
|
72
|
-
<AtomicIcon name="star" size="xl" color="primary" />
|
|
73
|
-
</View>
|
|
74
|
-
|
|
75
|
-
<AtomicText
|
|
76
|
-
type="headlineMedium"
|
|
77
|
-
color="onSurface"
|
|
78
|
-
style={[
|
|
79
|
-
styles.title,
|
|
80
|
-
{ marginBottom: tokens.spacing.sm },
|
|
81
|
-
]}
|
|
82
|
-
>
|
|
83
|
-
{defaultTranslations.title}
|
|
84
|
-
</AtomicText>
|
|
85
|
-
|
|
86
|
-
<AtomicText
|
|
87
|
-
type="bodyMedium"
|
|
88
|
-
color="onSurfaceVariant"
|
|
89
|
-
style={[
|
|
90
|
-
styles.message,
|
|
91
|
-
{ marginBottom: tokens.spacing.lg },
|
|
92
|
-
]}
|
|
93
|
-
>
|
|
94
|
-
{defaultTranslations.message}
|
|
95
|
-
</AtomicText>
|
|
96
|
-
|
|
97
|
-
<View style={[styles.buttonContainer, { gap: tokens.spacing.sm }]}>
|
|
98
|
-
<AtomicButton
|
|
99
|
-
variant="primary"
|
|
100
|
-
onPress={onPositive}
|
|
101
|
-
style={styles.button}
|
|
102
|
-
>
|
|
103
|
-
{defaultTranslations.positiveButton}
|
|
104
|
-
</AtomicButton>
|
|
105
|
-
|
|
106
|
-
<AtomicButton
|
|
107
|
-
variant="outline"
|
|
108
|
-
onPress={onNegative}
|
|
109
|
-
style={styles.button}
|
|
110
|
-
>
|
|
111
|
-
{defaultTranslations.negativeButton}
|
|
112
|
-
</AtomicButton>
|
|
113
|
-
|
|
114
|
-
<AtomicButton
|
|
115
|
-
variant="text"
|
|
116
|
-
onPress={onLater}
|
|
117
|
-
style={styles.button}
|
|
118
|
-
>
|
|
119
|
-
{defaultTranslations.laterButton}
|
|
120
|
-
</AtomicButton>
|
|
121
|
-
</View>
|
|
122
|
-
</View>
|
|
123
|
-
</View>
|
|
124
|
-
</Modal>
|
|
125
|
-
);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const styles = StyleSheet.create({
|
|
129
|
-
overlay: {
|
|
130
|
-
flex: 1,
|
|
131
|
-
justifyContent: "center",
|
|
132
|
-
alignItems: "center",
|
|
133
|
-
},
|
|
134
|
-
container: {
|
|
135
|
-
alignItems: "center",
|
|
136
|
-
},
|
|
137
|
-
iconContainer: {
|
|
138
|
-
marginBottom: 16,
|
|
139
|
-
},
|
|
140
|
-
title: {
|
|
141
|
-
textAlign: "center",
|
|
142
|
-
},
|
|
143
|
-
message: {
|
|
144
|
-
textAlign: "center",
|
|
145
|
-
},
|
|
146
|
-
buttonContainer: {
|
|
147
|
-
width: "100%",
|
|
148
|
-
},
|
|
149
|
-
button: {
|
|
150
|
-
width: "100%",
|
|
151
|
-
},
|
|
152
|
-
});
|