@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,37 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Settings Service
|
|
3
3
|
*
|
|
4
|
-
* Orchestrates settings operations using SettingsRepository
|
|
4
|
+
* Orchestrates settings operations using SettingsRepository.
|
|
5
|
+
* Refactored to extend BaseService for consistent error handling.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { SettingsRepository } from '../repositories/SettingsRepository';
|
|
9
|
+
import { BaseService } from '../../core/base/BaseService';
|
|
8
10
|
import type { UserSettings, SettingsResult } from '../../application/ports/ISettingsRepository';
|
|
9
11
|
|
|
10
|
-
export class SettingsService {
|
|
11
|
-
|
|
12
|
+
export class SettingsService extends BaseService {
|
|
13
|
+
protected serviceName = 'SettingsService';
|
|
14
|
+
private repository: SettingsRepository;
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
this.repository = new SettingsRepository();
|
|
19
|
+
}
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
async getSettings(userId: string): Promise<SettingsResult<UserSettings>> {
|
|
22
|
+
return await this.repository.getSettings(userId);
|
|
23
|
+
}
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
async saveSettings(settings: UserSettings): Promise<SettingsResult<void>> {
|
|
26
|
+
return await this.repository.saveSettings(settings);
|
|
27
|
+
}
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
async resetSettings(userId: string): Promise<SettingsResult<void>> {
|
|
30
|
+
return await this.repository.deleteSettings(userId);
|
|
31
|
+
}
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
let settingsServiceInstance: SettingsService | null = null;
|
|
31
35
|
|
|
32
36
|
export function getSettingsService(): SettingsService {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
if (!settingsServiceInstance) {
|
|
38
|
+
settingsServiceInstance = new SettingsService();
|
|
39
|
+
}
|
|
40
|
+
return settingsServiceInstance;
|
|
37
41
|
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenericModal Component
|
|
3
|
+
*
|
|
4
|
+
* Universal modal component that works with ModalConfig.
|
|
5
|
+
* Replaces all custom modal implementations.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const modal = useModalState();
|
|
10
|
+
*
|
|
11
|
+
* return (
|
|
12
|
+
* <>
|
|
13
|
+
* <Button onPress={() => modal.show(ModalPresets.confirm(...))} />
|
|
14
|
+
* <GenericModal state={modal} />
|
|
15
|
+
* </>
|
|
16
|
+
* );
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import React, { useMemo } from 'react';
|
|
21
|
+
import { View, StyleSheet } from 'react-native';
|
|
22
|
+
import { BaseModal } from '@umituz/react-native-design-system/molecules';
|
|
23
|
+
import { AtomicText, AtomicButton, AtomicIcon } from '@umituz/react-native-design-system/atoms';
|
|
24
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system/theme';
|
|
25
|
+
import { useResponsive } from '@umituz/react-native-design-system/responsive';
|
|
26
|
+
import type { ModalState } from '../../core/patterns/Modal/ModalConfig';
|
|
27
|
+
|
|
28
|
+
export interface GenericModalProps {
|
|
29
|
+
/**
|
|
30
|
+
* Modal state from useModalState hook
|
|
31
|
+
*/
|
|
32
|
+
state: ModalState;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Custom container style
|
|
36
|
+
*/
|
|
37
|
+
containerStyle?: object;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Test ID for E2E testing
|
|
41
|
+
*/
|
|
42
|
+
testID?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const GenericModal: React.FC<GenericModalProps> = ({
|
|
46
|
+
state,
|
|
47
|
+
containerStyle,
|
|
48
|
+
testID = 'generic-modal',
|
|
49
|
+
}) => {
|
|
50
|
+
const tokens = useAppDesignTokens();
|
|
51
|
+
const responsive = useResponsive();
|
|
52
|
+
const { visible, config } = state;
|
|
53
|
+
|
|
54
|
+
const styles = useMemo(() => getStyles(tokens), [tokens]);
|
|
55
|
+
|
|
56
|
+
if (!config) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const {
|
|
61
|
+
title,
|
|
62
|
+
subtitle,
|
|
63
|
+
message,
|
|
64
|
+
icon,
|
|
65
|
+
iconColor,
|
|
66
|
+
header,
|
|
67
|
+
body,
|
|
68
|
+
footer,
|
|
69
|
+
actions = [],
|
|
70
|
+
dismissible = true,
|
|
71
|
+
maxWidth,
|
|
72
|
+
} = config;
|
|
73
|
+
|
|
74
|
+
const handleDismiss = () => {
|
|
75
|
+
if (dismissible) {
|
|
76
|
+
state.hide();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const modalWidth = maxWidth ?? responsive.maxContentWidth * 0.9;
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<BaseModal
|
|
84
|
+
visible={visible}
|
|
85
|
+
onClose={handleDismiss}
|
|
86
|
+
>
|
|
87
|
+
<View
|
|
88
|
+
style={[
|
|
89
|
+
styles.container,
|
|
90
|
+
{
|
|
91
|
+
backgroundColor: tokens.colors.surface,
|
|
92
|
+
borderRadius: tokens.borders.radius.xl,
|
|
93
|
+
padding: tokens.spacing.lg,
|
|
94
|
+
maxWidth: modalWidth,
|
|
95
|
+
width: '90%',
|
|
96
|
+
},
|
|
97
|
+
containerStyle,
|
|
98
|
+
]}
|
|
99
|
+
testID={testID}
|
|
100
|
+
>
|
|
101
|
+
{/* Custom Header */}
|
|
102
|
+
{header ? (
|
|
103
|
+
header
|
|
104
|
+
) : (
|
|
105
|
+
<>
|
|
106
|
+
{/* Icon */}
|
|
107
|
+
{icon && (
|
|
108
|
+
<View style={styles.iconContainer}>
|
|
109
|
+
<AtomicIcon
|
|
110
|
+
name={icon}
|
|
111
|
+
size="xl"
|
|
112
|
+
color={iconColor as any || 'primary'}
|
|
113
|
+
/>
|
|
114
|
+
</View>
|
|
115
|
+
)}
|
|
116
|
+
|
|
117
|
+
{/* Title and Subtitle */}
|
|
118
|
+
{title && (
|
|
119
|
+
<AtomicText
|
|
120
|
+
type="headlineMedium"
|
|
121
|
+
color="onSurface"
|
|
122
|
+
style={[styles.title, { marginBottom: subtitle ? tokens.spacing.xs : tokens.spacing.md }]}
|
|
123
|
+
>
|
|
124
|
+
{title}
|
|
125
|
+
</AtomicText>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
{subtitle && (
|
|
129
|
+
<AtomicText
|
|
130
|
+
type="bodyMedium"
|
|
131
|
+
color="onSurfaceVariant"
|
|
132
|
+
style={[styles.subtitle, { marginBottom: tokens.spacing.md }]}
|
|
133
|
+
>
|
|
134
|
+
{subtitle}
|
|
135
|
+
</AtomicText>
|
|
136
|
+
)}
|
|
137
|
+
</>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{/* Custom Body or Message */}
|
|
141
|
+
{body ? (
|
|
142
|
+
body
|
|
143
|
+
) : message && (
|
|
144
|
+
<AtomicText
|
|
145
|
+
type="bodyMedium"
|
|
146
|
+
color="onSurfaceVariant"
|
|
147
|
+
style={[styles.message, { marginBottom: actions.length > 0 ? tokens.spacing.lg : 0 }]}
|
|
148
|
+
>
|
|
149
|
+
{message}
|
|
150
|
+
</AtomicText>
|
|
151
|
+
)}
|
|
152
|
+
|
|
153
|
+
{/* Custom Footer or Actions */}
|
|
154
|
+
{footer ? (
|
|
155
|
+
footer
|
|
156
|
+
) : actions.length > 0 && (
|
|
157
|
+
<View style={[styles.buttonContainer, { gap: tokens.spacing.sm }]}>
|
|
158
|
+
{actions.map((action, index) => (
|
|
159
|
+
<AtomicButton
|
|
160
|
+
key={index}
|
|
161
|
+
variant={action.variant || 'primary'}
|
|
162
|
+
onPress={async () => {
|
|
163
|
+
await action.onPress();
|
|
164
|
+
if (dismissible) {
|
|
165
|
+
state.hide();
|
|
166
|
+
}
|
|
167
|
+
}}
|
|
168
|
+
disabled={action.disabled}
|
|
169
|
+
loading={action.loading}
|
|
170
|
+
style={styles.button}
|
|
171
|
+
testID={action.testID}
|
|
172
|
+
>
|
|
173
|
+
{action.label}
|
|
174
|
+
</AtomicButton>
|
|
175
|
+
))}
|
|
176
|
+
</View>
|
|
177
|
+
)}
|
|
178
|
+
</View>
|
|
179
|
+
</BaseModal>
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const getStyles = (_tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
184
|
+
StyleSheet.create({
|
|
185
|
+
container: {
|
|
186
|
+
alignItems: 'center',
|
|
187
|
+
},
|
|
188
|
+
iconContainer: {
|
|
189
|
+
marginBottom: 16,
|
|
190
|
+
},
|
|
191
|
+
title: {
|
|
192
|
+
textAlign: 'center',
|
|
193
|
+
},
|
|
194
|
+
subtitle: {
|
|
195
|
+
textAlign: 'center',
|
|
196
|
+
},
|
|
197
|
+
message: {
|
|
198
|
+
textAlign: 'center',
|
|
199
|
+
lineHeight: 24,
|
|
200
|
+
},
|
|
201
|
+
buttonContainer: {
|
|
202
|
+
width: '100%',
|
|
203
|
+
marginTop: 16,
|
|
204
|
+
},
|
|
205
|
+
button: {
|
|
206
|
+
width: '100%',
|
|
207
|
+
},
|
|
208
|
+
});
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenericScreen Component
|
|
3
|
+
*
|
|
4
|
+
* Universal screen component that works with ScreenConfig.
|
|
5
|
+
* Handles loading, error, and empty states automatically.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const screenData = useScreenData({ fetch: async () => await api.getData() });
|
|
10
|
+
*
|
|
11
|
+
* return (
|
|
12
|
+
* <GenericScreen
|
|
13
|
+
* data={screenData}
|
|
14
|
+
* config={ScreenPresets.default}
|
|
15
|
+
* >
|
|
16
|
+
* <MyContent data={screenData.data} />
|
|
17
|
+
* </GenericScreen>
|
|
18
|
+
* );
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import React, { useMemo } from 'react';
|
|
23
|
+
import { View, StyleSheet } from 'react-native';
|
|
24
|
+
import { ScreenLayout } from '@umituz/react-native-design-system/layouts';
|
|
25
|
+
import {
|
|
26
|
+
NavigationHeader,
|
|
27
|
+
useAppNavigation,
|
|
28
|
+
} from '@umituz/react-native-design-system/molecules';
|
|
29
|
+
import {
|
|
30
|
+
AtomicText,
|
|
31
|
+
AtomicButton,
|
|
32
|
+
AtomicSpinner,
|
|
33
|
+
AtomicIcon,
|
|
34
|
+
} from '@umituz/react-native-design-system/atoms';
|
|
35
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system/theme';
|
|
36
|
+
import type {
|
|
37
|
+
ScreenConfig,
|
|
38
|
+
ScreenData,
|
|
39
|
+
} from '../../core/patterns/Screen/ScreenConfig';
|
|
40
|
+
|
|
41
|
+
export interface GenericScreenProps<T> {
|
|
42
|
+
/**
|
|
43
|
+
* Screen data from useScreenData hook
|
|
44
|
+
*/
|
|
45
|
+
data: ScreenData<T>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Screen configuration
|
|
49
|
+
*/
|
|
50
|
+
config?: ScreenConfig;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Content component (renders when data is loaded)
|
|
54
|
+
*/
|
|
55
|
+
children: React.ReactNode | ((data: T) => React.ReactNode);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Test ID for E2E testing
|
|
59
|
+
*/
|
|
60
|
+
testID?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function GenericScreen<T>({
|
|
64
|
+
data,
|
|
65
|
+
config = {},
|
|
66
|
+
children,
|
|
67
|
+
testID = 'generic-screen',
|
|
68
|
+
}: GenericScreenProps<T>) {
|
|
69
|
+
const tokens = useAppDesignTokens();
|
|
70
|
+
const navigation = useAppNavigation();
|
|
71
|
+
const styles = useMemo(() => getStyles(tokens), [tokens]);
|
|
72
|
+
|
|
73
|
+
const {
|
|
74
|
+
header,
|
|
75
|
+
loading = {},
|
|
76
|
+
error = {},
|
|
77
|
+
empty,
|
|
78
|
+
layout = {},
|
|
79
|
+
} = config;
|
|
80
|
+
|
|
81
|
+
// Header configuration
|
|
82
|
+
const showBackButton = header?.showBackButton !== false;
|
|
83
|
+
const onBackPress = header?.onBackPress || (() => navigation.goBack());
|
|
84
|
+
|
|
85
|
+
const navigationHeader = header?.custom ? (
|
|
86
|
+
header.custom
|
|
87
|
+
) : header?.title || showBackButton ? (
|
|
88
|
+
<NavigationHeader
|
|
89
|
+
title={header?.title || ''}
|
|
90
|
+
onBackPress={showBackButton ? onBackPress : undefined}
|
|
91
|
+
/>
|
|
92
|
+
) : undefined;
|
|
93
|
+
|
|
94
|
+
// Loading state
|
|
95
|
+
if (data.loading) {
|
|
96
|
+
return (
|
|
97
|
+
<ScreenLayout
|
|
98
|
+
edges={layout.edges || ['top', 'bottom', 'left', 'right']}
|
|
99
|
+
header={navigationHeader}
|
|
100
|
+
scrollable={layout.scrollable}
|
|
101
|
+
keyboardAvoiding={layout.keyboardAvoiding}
|
|
102
|
+
hideScrollIndicator={layout.hideScrollIndicator}
|
|
103
|
+
contentContainerStyle={layout.contentContainerStyle}
|
|
104
|
+
testID={testID}
|
|
105
|
+
>
|
|
106
|
+
{loading.custom ? (
|
|
107
|
+
loading.custom
|
|
108
|
+
) : (
|
|
109
|
+
<View style={styles.centerContainer}>
|
|
110
|
+
<AtomicSpinner
|
|
111
|
+
size={loading.size || 'lg'}
|
|
112
|
+
fullContainer={loading.fullContainer !== false}
|
|
113
|
+
/>
|
|
114
|
+
{loading.message && (
|
|
115
|
+
<AtomicText
|
|
116
|
+
type="bodyMedium"
|
|
117
|
+
color="onSurfaceVariant"
|
|
118
|
+
style={styles.stateMessage}
|
|
119
|
+
>
|
|
120
|
+
{loading.message}
|
|
121
|
+
</AtomicText>
|
|
122
|
+
)}
|
|
123
|
+
</View>
|
|
124
|
+
)}
|
|
125
|
+
</ScreenLayout>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Error state
|
|
130
|
+
if (data.error) {
|
|
131
|
+
return (
|
|
132
|
+
<ScreenLayout
|
|
133
|
+
edges={layout.edges || ['top', 'bottom', 'left', 'right']}
|
|
134
|
+
header={navigationHeader}
|
|
135
|
+
scrollable={layout.scrollable}
|
|
136
|
+
keyboardAvoiding={layout.keyboardAvoiding}
|
|
137
|
+
hideScrollIndicator={layout.hideScrollIndicator}
|
|
138
|
+
contentContainerStyle={layout.contentContainerStyle}
|
|
139
|
+
testID={testID}
|
|
140
|
+
>
|
|
141
|
+
{error.custom ? (
|
|
142
|
+
error.custom
|
|
143
|
+
) : (
|
|
144
|
+
<View style={styles.centerContainer}>
|
|
145
|
+
<AtomicIcon
|
|
146
|
+
name={error.icon || 'error'}
|
|
147
|
+
size="xl"
|
|
148
|
+
color="error"
|
|
149
|
+
style={styles.stateIcon}
|
|
150
|
+
/>
|
|
151
|
+
<AtomicText
|
|
152
|
+
type="headlineMedium"
|
|
153
|
+
color="onSurface"
|
|
154
|
+
style={styles.stateTitle}
|
|
155
|
+
>
|
|
156
|
+
{error.prefix || 'Error'}
|
|
157
|
+
</AtomicText>
|
|
158
|
+
<AtomicText
|
|
159
|
+
type="bodyMedium"
|
|
160
|
+
color="onSurfaceVariant"
|
|
161
|
+
style={styles.stateMessage}
|
|
162
|
+
>
|
|
163
|
+
{error.message || data.error}
|
|
164
|
+
</AtomicText>
|
|
165
|
+
{error.showRetry && (
|
|
166
|
+
<AtomicButton
|
|
167
|
+
variant="outline"
|
|
168
|
+
onPress={error.onRetry || data.refresh}
|
|
169
|
+
style={styles.retryButton}
|
|
170
|
+
>
|
|
171
|
+
{error.retryText || 'Retry'}
|
|
172
|
+
</AtomicButton>
|
|
173
|
+
)}
|
|
174
|
+
</View>
|
|
175
|
+
)}
|
|
176
|
+
</ScreenLayout>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Empty state
|
|
181
|
+
if (!data.data && empty) {
|
|
182
|
+
return (
|
|
183
|
+
<ScreenLayout
|
|
184
|
+
edges={layout.edges || ['top', 'bottom', 'left', 'right']}
|
|
185
|
+
header={navigationHeader}
|
|
186
|
+
scrollable={layout.scrollable}
|
|
187
|
+
keyboardAvoiding={layout.keyboardAvoiding}
|
|
188
|
+
hideScrollIndicator={layout.hideScrollIndicator}
|
|
189
|
+
contentContainerStyle={layout.contentContainerStyle}
|
|
190
|
+
testID={testID}
|
|
191
|
+
>
|
|
192
|
+
{empty.custom ? (
|
|
193
|
+
empty.custom
|
|
194
|
+
) : (
|
|
195
|
+
<View style={styles.centerContainer}>
|
|
196
|
+
{empty.icon && (
|
|
197
|
+
<AtomicIcon
|
|
198
|
+
name={empty.icon}
|
|
199
|
+
size="xl"
|
|
200
|
+
color="onSurfaceVariant"
|
|
201
|
+
style={styles.stateIcon}
|
|
202
|
+
/>
|
|
203
|
+
)}
|
|
204
|
+
<AtomicText
|
|
205
|
+
type="headlineMedium"
|
|
206
|
+
color="onSurface"
|
|
207
|
+
style={styles.stateTitle}
|
|
208
|
+
>
|
|
209
|
+
{empty.message || 'No Data'}
|
|
210
|
+
</AtomicText>
|
|
211
|
+
{empty.description && (
|
|
212
|
+
<AtomicText
|
|
213
|
+
type="bodyMedium"
|
|
214
|
+
color="onSurfaceVariant"
|
|
215
|
+
style={styles.stateMessage}
|
|
216
|
+
>
|
|
217
|
+
{empty.description}
|
|
218
|
+
</AtomicText>
|
|
219
|
+
)}
|
|
220
|
+
{empty.actionLabel && empty.onAction && (
|
|
221
|
+
<AtomicButton
|
|
222
|
+
variant="outline"
|
|
223
|
+
onPress={empty.onAction}
|
|
224
|
+
style={styles.retryButton}
|
|
225
|
+
>
|
|
226
|
+
{empty.actionLabel}
|
|
227
|
+
</AtomicButton>
|
|
228
|
+
)}
|
|
229
|
+
</View>
|
|
230
|
+
)}
|
|
231
|
+
</ScreenLayout>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Content
|
|
236
|
+
return (
|
|
237
|
+
<ScreenLayout
|
|
238
|
+
edges={layout.edges || ['top', 'bottom', 'left', 'right']}
|
|
239
|
+
header={navigationHeader}
|
|
240
|
+
scrollable={layout.scrollable}
|
|
241
|
+
keyboardAvoiding={layout.keyboardAvoiding}
|
|
242
|
+
hideScrollIndicator={layout.hideScrollIndicator}
|
|
243
|
+
contentContainerStyle={layout.contentContainerStyle}
|
|
244
|
+
testID={testID}
|
|
245
|
+
>
|
|
246
|
+
{typeof children === 'function' ? (children as (data: T) => React.ReactNode)(data.data!) : children}
|
|
247
|
+
</ScreenLayout>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const getStyles = (_tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
252
|
+
StyleSheet.create({
|
|
253
|
+
centerContainer: {
|
|
254
|
+
flex: 1,
|
|
255
|
+
justifyContent: 'center',
|
|
256
|
+
alignItems: 'center',
|
|
257
|
+
paddingHorizontal: 32,
|
|
258
|
+
},
|
|
259
|
+
stateIcon: {
|
|
260
|
+
marginBottom: 16,
|
|
261
|
+
},
|
|
262
|
+
stateTitle: {
|
|
263
|
+
marginBottom: 8,
|
|
264
|
+
textAlign: 'center',
|
|
265
|
+
},
|
|
266
|
+
stateMessage: {
|
|
267
|
+
textAlign: 'center',
|
|
268
|
+
marginBottom: 24,
|
|
269
|
+
},
|
|
270
|
+
retryButton: {
|
|
271
|
+
minWidth: 120,
|
|
272
|
+
},
|
|
273
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation Components - Public API
|
|
3
|
+
*
|
|
4
|
+
* Shared UI components for the entire application.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { GenericModal, GenericScreen } from '@/presentation/components';
|
|
9
|
+
*
|
|
10
|
+
* const modal = useModalState();
|
|
11
|
+
* return <GenericModal state={modal} />;
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// MODAL COMPONENTS
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
export { GenericModal } from './GenericModal';
|
|
20
|
+
export type { GenericModalProps } from './GenericModal';
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// SCREEN COMPONENTS
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
export { GenericScreen } from './GenericScreen';
|
|
27
|
+
export type { GenericScreenProps } from './GenericScreen';
|
|
@@ -3,6 +3,9 @@ import type { StackScreen } from "@umituz/react-native-design-system/molecules";
|
|
|
3
3
|
import { LanguageSelectionScreen } from "../../../domains/localization";
|
|
4
4
|
import { NotificationSettingsScreen } from "../../../domains/notifications";
|
|
5
5
|
import { SettingsScreen } from "../../screens/SettingsScreen";
|
|
6
|
+
import { DisclaimerScreen } from "../../../domains/disclaimer/presentation/screens/DisclaimerScreen";
|
|
7
|
+
import { FeedbackScreen } from "../../../domains/feedback/presentation/screens/FeedbackScreen";
|
|
8
|
+
import { RatingPromptScreen } from "../../../domains/rating/presentation/screens/RatingPromptScreen";
|
|
6
9
|
|
|
7
10
|
// AccountScreen is an optional peer — lazy require so the package works without @umituz/react-native-auth
|
|
8
11
|
// Returns null if @umituz/react-native-auth is not installed
|
|
@@ -176,6 +179,25 @@ export const useSettingsScreens = (props: UseSettingsScreensProps): StackScreen[
|
|
|
176
179
|
}
|
|
177
180
|
});
|
|
178
181
|
|
|
182
|
+
// New screens to replace modals
|
|
183
|
+
const disclaimerScreen = {
|
|
184
|
+
name: "Disclaimer" as const,
|
|
185
|
+
component: DisclaimerScreen,
|
|
186
|
+
options: { headerShown: false },
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const feedbackScreen = {
|
|
190
|
+
name: "Feedback" as const,
|
|
191
|
+
component: FeedbackScreen,
|
|
192
|
+
options: { headerShown: false },
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const ratingPromptScreen = {
|
|
196
|
+
name: "RatingPrompt" as const,
|
|
197
|
+
component: RatingPromptScreen,
|
|
198
|
+
options: { headerShown: false },
|
|
199
|
+
};
|
|
200
|
+
|
|
179
201
|
return combineScreens(
|
|
180
202
|
baseScreens,
|
|
181
203
|
faqScreen,
|
|
@@ -184,7 +206,10 @@ export const useSettingsScreens = (props: UseSettingsScreensProps): StackScreen[
|
|
|
184
206
|
languageScreen,
|
|
185
207
|
accountScreen,
|
|
186
208
|
videoTutorialScreen,
|
|
187
|
-
featureRequestScreen
|
|
209
|
+
featureRequestScreen,
|
|
210
|
+
disclaimerScreen,
|
|
211
|
+
feedbackScreen,
|
|
212
|
+
ratingPromptScreen
|
|
188
213
|
);
|
|
189
214
|
}, [
|
|
190
215
|
translations,
|
|
@@ -6,6 +6,9 @@ import type React from 'react';
|
|
|
6
6
|
import type { SettingsConfig, CustomSettingsSection } from "../screens/types";
|
|
7
7
|
import type { DevSettingsProps } from "../../domains/dev";
|
|
8
8
|
import type { FAQCategory } from "../../domains/faqs";
|
|
9
|
+
import type { DisclaimerScreenParams } from "../../domains/disclaimer/presentation/screens/DisclaimerScreen";
|
|
10
|
+
import type { FeedbackScreenParams } from "../../domains/feedback/presentation/screens/FeedbackScreen";
|
|
11
|
+
import type { RatingPromptScreenParams } from "../../domains/rating/presentation/screens/RatingPromptScreen";
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* App Info passed from main app (APP_INFO constant)
|
|
@@ -48,6 +51,9 @@ export type SettingsStackParamList = {
|
|
|
48
51
|
Account: undefined;
|
|
49
52
|
VideoTutorial: undefined;
|
|
50
53
|
FeatureRequest: undefined;
|
|
54
|
+
Disclaimer: DisclaimerScreenParams;
|
|
55
|
+
Feedback: FeedbackScreenParams;
|
|
56
|
+
RatingPrompt: RatingPromptScreenParams;
|
|
51
57
|
PasswordPrompt: {
|
|
52
58
|
onComplete: (password: string | null) => void;
|
|
53
59
|
title?: string;
|