@umituz/react-native-settings 5.4.10 → 5.4.12
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/index.ts +1 -1
- package/src/core/patterns/Screen/useScreenData.ts +0 -1
- package/src/core/utils/logger.ts +5 -5
- 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 -2
- package/src/domains/feedback/presentation/components/SupportSection.tsx +16 -44
- package/src/domains/feedback/presentation/screens/FeatureRequestScreen.tsx +13 -35
- package/src/domains/feedback/presentation/screens/FeedbackScreen.tsx +75 -0
- package/src/domains/notifications/infrastructure/services/NotificationService.ts +2 -4
- package/src/domains/rating/application/services/RatingService.ts +31 -0
- 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/infrastructure/services/SettingsService.ts +3 -9
- package/src/presentation/components/GenericModal.tsx +3 -7
- package/src/presentation/components/GenericScreen.tsx +1 -6
- package/src/presentation/navigation/hooks/useSettingsScreens.ts +26 -1
- package/src/presentation/navigation/types.ts +6 -0
- package/src/domains/disclaimer/presentation/hooks/useDisclaimerModal.ts +0 -72
- package/src/domains/feedback/presentation/hooks/useFeedbackModal.ts +0 -182
- package/src/domains/rating/presentation/hooks/useRatingPromptModal.ts +0 -122
|
@@ -34,11 +34,11 @@ export { StarRating } from './presentation/components/StarRating';
|
|
|
34
34
|
export type { StarRatingProps } from './presentation/components/StarRating';
|
|
35
35
|
|
|
36
36
|
// =============================================================================
|
|
37
|
-
// PRESENTATION LAYER -
|
|
37
|
+
// PRESENTATION LAYER - Screens
|
|
38
38
|
// =============================================================================
|
|
39
39
|
|
|
40
|
-
export {
|
|
41
|
-
export type {
|
|
40
|
+
export { RatingPromptScreen } from './presentation/screens/RatingPromptScreen';
|
|
41
|
+
export type { RatingPromptScreenProps, RatingPromptScreenParams } from './presentation/screens/RatingPromptScreen';
|
|
42
42
|
|
|
43
43
|
// =============================================================================
|
|
44
44
|
// PRESENTATION LAYER - Hooks (App Store Rating)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Lazy loads expo-store-review to reduce bundle size
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import { useCallback, useMemo } from "react";
|
|
8
8
|
import type {
|
|
9
9
|
RatingConfig,
|
|
10
10
|
UseAppRatingResult,
|
|
@@ -12,14 +12,14 @@ import type {
|
|
|
12
12
|
} from "../../domain/entities/RatingConfig";
|
|
13
13
|
import { DEFAULT_RATING_CONFIG } from "../../domain/entities/RatingConfig";
|
|
14
14
|
import * as RatingService from "../../application/services/RatingService";
|
|
15
|
-
import {
|
|
15
|
+
import { useAppNavigation } from "@umituz/react-native-design-system/molecules";
|
|
16
16
|
import { isDev } from "../../../../utils/devUtils";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* App rating hook with
|
|
19
|
+
* App rating hook with navigation-based prompt flow
|
|
20
20
|
*/
|
|
21
21
|
export function useAppRating(config: RatingConfig): UseAppRatingResult {
|
|
22
|
-
const
|
|
22
|
+
const navigation = useAppNavigation();
|
|
23
23
|
|
|
24
24
|
const mergedConfig = useMemo<RatingConfig>(() => ({
|
|
25
25
|
...DEFAULT_RATING_CONFIG,
|
|
@@ -35,13 +35,48 @@ export function useAppRating(config: RatingConfig): UseAppRatingResult {
|
|
|
35
35
|
}, [mergedConfig]);
|
|
36
36
|
|
|
37
37
|
const showPrompt = useCallback(async (): Promise<void> => {
|
|
38
|
-
setIsVisible(true);
|
|
39
38
|
await RatingService.markPromptShown(mergedConfig.eventType);
|
|
40
39
|
|
|
40
|
+
navigation.push('RatingPrompt' as never, {
|
|
41
|
+
appName: mergedConfig.appName,
|
|
42
|
+
translations: mergedConfig.translations,
|
|
43
|
+
onPositive: async () => {
|
|
44
|
+
await RatingService.markRated();
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Lazy load expo-store-review
|
|
48
|
+
const StoreReview = await import('expo-store-review');
|
|
49
|
+
const isAvailable = await StoreReview.isAvailableAsync();
|
|
50
|
+
|
|
51
|
+
if (isAvailable) {
|
|
52
|
+
await StoreReview.requestReview();
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (isDev()) {
|
|
56
|
+
console.error('[useAppRating] Failed to request review:', error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (mergedConfig.onPositiveFeedback) {
|
|
61
|
+
await mergedConfig.onPositiveFeedback();
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
onNegative: async () => {
|
|
65
|
+
if (mergedConfig.onNegativeFeedback) {
|
|
66
|
+
await mergedConfig.onNegativeFeedback();
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
onLater: async () => {
|
|
70
|
+
if (mergedConfig.onPromptDismissed) {
|
|
71
|
+
await mergedConfig.onPromptDismissed();
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
41
76
|
if (mergedConfig.onPromptShown) {
|
|
42
77
|
await mergedConfig.onPromptShown();
|
|
43
78
|
}
|
|
44
|
-
}, [mergedConfig]);
|
|
79
|
+
}, [mergedConfig, navigation]);
|
|
45
80
|
|
|
46
81
|
const checkAndShow = useCallback(async (): Promise<boolean> => {
|
|
47
82
|
const should = await shouldShow();
|
|
@@ -62,62 +97,6 @@ export function useAppRating(config: RatingConfig): UseAppRatingResult {
|
|
|
62
97
|
return RatingService.getState(mergedConfig.eventType);
|
|
63
98
|
}, [mergedConfig.eventType]);
|
|
64
99
|
|
|
65
|
-
const handlePositive = useCallback(async () => {
|
|
66
|
-
setIsVisible(false);
|
|
67
|
-
await RatingService.markRated();
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
// Lazy load expo-store-review
|
|
71
|
-
const StoreReview = await import('expo-store-review');
|
|
72
|
-
const isAvailable = await StoreReview.isAvailableAsync();
|
|
73
|
-
|
|
74
|
-
if (isAvailable) {
|
|
75
|
-
await StoreReview.requestReview();
|
|
76
|
-
}
|
|
77
|
-
} catch (error) {
|
|
78
|
-
if (isDev()) {
|
|
79
|
-
console.error('[useAppRating] Failed to request review:', error);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (mergedConfig.onPositiveFeedback) {
|
|
84
|
-
await mergedConfig.onPositiveFeedback();
|
|
85
|
-
}
|
|
86
|
-
}, [mergedConfig]);
|
|
87
|
-
|
|
88
|
-
const handleNegative = useCallback(async () => {
|
|
89
|
-
setIsVisible(false);
|
|
90
|
-
|
|
91
|
-
if (mergedConfig.onNegativeFeedback) {
|
|
92
|
-
await mergedConfig.onNegativeFeedback();
|
|
93
|
-
}
|
|
94
|
-
}, [mergedConfig]);
|
|
95
|
-
|
|
96
|
-
const handleLater = useCallback(() => {
|
|
97
|
-
setIsVisible(false);
|
|
98
|
-
}, []);
|
|
99
|
-
|
|
100
|
-
const handleDismiss = useCallback(async () => {
|
|
101
|
-
setIsVisible(false);
|
|
102
|
-
await RatingService.markDismissed();
|
|
103
|
-
|
|
104
|
-
if (mergedConfig.onPromptDismissed) {
|
|
105
|
-
await mergedConfig.onPromptDismissed();
|
|
106
|
-
}
|
|
107
|
-
}, [mergedConfig]);
|
|
108
|
-
|
|
109
|
-
const modal = (
|
|
110
|
-
<RatingPromptModal
|
|
111
|
-
visible={isVisible}
|
|
112
|
-
onPositive={handlePositive}
|
|
113
|
-
onNegative={handleNegative}
|
|
114
|
-
onLater={handleLater}
|
|
115
|
-
onDismiss={handleDismiss}
|
|
116
|
-
translations={mergedConfig.translations}
|
|
117
|
-
appName={mergedConfig.appName}
|
|
118
|
-
/>
|
|
119
|
-
);
|
|
120
|
-
|
|
121
100
|
return {
|
|
122
101
|
trackEvent,
|
|
123
102
|
checkAndShow,
|
|
@@ -125,7 +104,5 @@ export function useAppRating(config: RatingConfig): UseAppRatingResult {
|
|
|
125
104
|
showPrompt,
|
|
126
105
|
reset,
|
|
127
106
|
getState,
|
|
128
|
-
|
|
129
|
-
modal,
|
|
130
|
-
} as UseAppRatingResult & { modal: React.ReactNode };
|
|
107
|
+
} as UseAppRatingResult;
|
|
131
108
|
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rating Prompt Screen
|
|
3
|
+
*
|
|
4
|
+
* Full screen for app rating prompt.
|
|
5
|
+
* Replaces modal approach with native navigation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { View, StyleSheet, ScrollView } from 'react-native';
|
|
10
|
+
import { ScreenLayout } from '@umituz/react-native-design-system/layouts';
|
|
11
|
+
import { AtomicText, AtomicButton, AtomicIcon } from '@umituz/react-native-design-system/atoms';
|
|
12
|
+
import { NavigationHeader, useAppNavigation } from '@umituz/react-native-design-system/molecules';
|
|
13
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system/theme';
|
|
14
|
+
import type { RatingTranslations } from '../../domain/entities/RatingConfig';
|
|
15
|
+
|
|
16
|
+
export interface RatingPromptScreenParams {
|
|
17
|
+
appName?: string;
|
|
18
|
+
translations?: RatingTranslations;
|
|
19
|
+
onPositive?: () => void;
|
|
20
|
+
onNegative?: () => void;
|
|
21
|
+
onLater?: () => void;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RatingPromptScreenProps {
|
|
26
|
+
route: {
|
|
27
|
+
params: RatingPromptScreenParams;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const RatingPromptScreen: React.FC<RatingPromptScreenProps> = ({ route }) => {
|
|
32
|
+
const navigation = useAppNavigation();
|
|
33
|
+
const tokens = useAppDesignTokens();
|
|
34
|
+
const styles = getStyles(tokens);
|
|
35
|
+
const { appName = 'this app', translations, onPositive, onNegative, onLater } = route.params;
|
|
36
|
+
|
|
37
|
+
const defaultTranslations: RatingTranslations = {
|
|
38
|
+
title: translations?.title ?? 'Enjoying the app?',
|
|
39
|
+
message:
|
|
40
|
+
translations?.message ??
|
|
41
|
+
`If you love using ${appName}, would you mind taking a moment to rate it?`,
|
|
42
|
+
positiveButton: translations?.positiveButton ?? 'Yes, I love it!',
|
|
43
|
+
negativeButton: translations?.negativeButton ?? 'Not really',
|
|
44
|
+
laterButton: translations?.laterButton ?? 'Maybe later',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const handlePositive = async () => {
|
|
48
|
+
try {
|
|
49
|
+
// Lazy load expo-store-review
|
|
50
|
+
const StoreReview = await import('expo-store-review');
|
|
51
|
+
const isAvailable = await StoreReview.isAvailableAsync();
|
|
52
|
+
|
|
53
|
+
if (isAvailable) {
|
|
54
|
+
await StoreReview.requestReview();
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('[RatingPromptScreen] Failed to request review:', error);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (onPositive) {
|
|
61
|
+
onPositive();
|
|
62
|
+
}
|
|
63
|
+
navigation.goBack();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleNegative = () => {
|
|
67
|
+
if (onNegative) {
|
|
68
|
+
onNegative();
|
|
69
|
+
}
|
|
70
|
+
navigation.goBack();
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const handleLater = () => {
|
|
74
|
+
if (onLater) {
|
|
75
|
+
onLater();
|
|
76
|
+
}
|
|
77
|
+
navigation.goBack();
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<ScreenLayout
|
|
82
|
+
scrollable={true}
|
|
83
|
+
edges={['top', 'bottom', 'left', 'right']}
|
|
84
|
+
hideScrollIndicator={false}
|
|
85
|
+
>
|
|
86
|
+
<NavigationHeader title="" />
|
|
87
|
+
<ScrollView
|
|
88
|
+
style={styles.scrollView}
|
|
89
|
+
contentContainerStyle={styles.scrollContent}
|
|
90
|
+
>
|
|
91
|
+
<View style={styles.iconContainer}>
|
|
92
|
+
<AtomicIcon name="star" size="xxl" color="primary" />
|
|
93
|
+
</View>
|
|
94
|
+
|
|
95
|
+
<AtomicText style={styles.title}>{defaultTranslations.title}</AtomicText>
|
|
96
|
+
|
|
97
|
+
<AtomicText style={styles.message}>{defaultTranslations.message}</AtomicText>
|
|
98
|
+
|
|
99
|
+
<View style={styles.buttonContainer}>
|
|
100
|
+
<AtomicButton
|
|
101
|
+
variant="primary"
|
|
102
|
+
onPress={handlePositive}
|
|
103
|
+
style={styles.button}
|
|
104
|
+
>
|
|
105
|
+
{defaultTranslations.positiveButton}
|
|
106
|
+
</AtomicButton>
|
|
107
|
+
|
|
108
|
+
<AtomicButton
|
|
109
|
+
variant="outline"
|
|
110
|
+
onPress={handleNegative}
|
|
111
|
+
style={styles.button}
|
|
112
|
+
>
|
|
113
|
+
{defaultTranslations.negativeButton}
|
|
114
|
+
</AtomicButton>
|
|
115
|
+
|
|
116
|
+
<AtomicButton
|
|
117
|
+
variant="text"
|
|
118
|
+
onPress={handleLater}
|
|
119
|
+
style={styles.button}
|
|
120
|
+
>
|
|
121
|
+
{defaultTranslations.laterButton}
|
|
122
|
+
</AtomicButton>
|
|
123
|
+
</View>
|
|
124
|
+
</ScrollView>
|
|
125
|
+
</ScreenLayout>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const getStyles = (_tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
130
|
+
StyleSheet.create({
|
|
131
|
+
scrollView: {
|
|
132
|
+
flex: 1,
|
|
133
|
+
},
|
|
134
|
+
scrollContent: {
|
|
135
|
+
padding: 20,
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
justifyContent: 'center',
|
|
138
|
+
minHeight: '100%',
|
|
139
|
+
},
|
|
140
|
+
iconContainer: {
|
|
141
|
+
marginBottom: 24,
|
|
142
|
+
},
|
|
143
|
+
title: {
|
|
144
|
+
fontSize: 24,
|
|
145
|
+
fontWeight: '700',
|
|
146
|
+
textAlign: 'center',
|
|
147
|
+
marginBottom: 16,
|
|
148
|
+
},
|
|
149
|
+
message: {
|
|
150
|
+
fontSize: 16,
|
|
151
|
+
textAlign: 'center',
|
|
152
|
+
marginBottom: 32,
|
|
153
|
+
lineHeight: 24,
|
|
154
|
+
},
|
|
155
|
+
buttonContainer: {
|
|
156
|
+
width: '100%',
|
|
157
|
+
gap: 12,
|
|
158
|
+
},
|
|
159
|
+
button: {
|
|
160
|
+
width: '100%',
|
|
161
|
+
},
|
|
162
|
+
});
|
|
@@ -19,21 +19,15 @@ export class SettingsService extends BaseService {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
async getSettings(userId: string): Promise<SettingsResult<UserSettings>> {
|
|
22
|
-
return this.
|
|
23
|
-
return await this.repository.getSettings(userId);
|
|
24
|
-
});
|
|
22
|
+
return await this.repository.getSettings(userId);
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
async saveSettings(settings: UserSettings): Promise<SettingsResult<void>> {
|
|
28
|
-
return this.
|
|
29
|
-
return await this.repository.saveSettings(settings);
|
|
30
|
-
});
|
|
26
|
+
return await this.repository.saveSettings(settings);
|
|
31
27
|
}
|
|
32
28
|
|
|
33
29
|
async resetSettings(userId: string): Promise<SettingsResult<void>> {
|
|
34
|
-
return this.
|
|
35
|
-
return await this.repository.deleteSettings(userId);
|
|
36
|
-
});
|
|
30
|
+
return await this.repository.deleteSettings(userId);
|
|
37
31
|
}
|
|
38
32
|
}
|
|
39
33
|
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import React, { useMemo } from 'react';
|
|
21
|
-
import { View, StyleSheet
|
|
21
|
+
import { View, StyleSheet } from 'react-native';
|
|
22
22
|
import { BaseModal } from '@umituz/react-native-design-system/molecules';
|
|
23
23
|
import { AtomicText, AtomicButton, AtomicIcon } from '@umituz/react-native-design-system/atoms';
|
|
24
24
|
import { useAppDesignTokens } from '@umituz/react-native-design-system/theme';
|
|
25
25
|
import { useResponsive } from '@umituz/react-native-design-system/responsive';
|
|
26
|
-
import type {
|
|
26
|
+
import type { ModalState } from '../../core/patterns/Modal/ModalConfig';
|
|
27
27
|
|
|
28
28
|
export interface GenericModalProps {
|
|
29
29
|
/**
|
|
@@ -68,8 +68,6 @@ export const GenericModal: React.FC<GenericModalProps> = ({
|
|
|
68
68
|
footer,
|
|
69
69
|
actions = [],
|
|
70
70
|
dismissible = true,
|
|
71
|
-
closeOnBackdropPress = true,
|
|
72
|
-
closeOnBackPress = true,
|
|
73
71
|
maxWidth,
|
|
74
72
|
} = config;
|
|
75
73
|
|
|
@@ -85,8 +83,6 @@ export const GenericModal: React.FC<GenericModalProps> = ({
|
|
|
85
83
|
<BaseModal
|
|
86
84
|
visible={visible}
|
|
87
85
|
onClose={handleDismiss}
|
|
88
|
-
closeOnBackdropPress={closeOnBackdropPress}
|
|
89
|
-
closeOnBackPress={closeOnBackPress}
|
|
90
86
|
>
|
|
91
87
|
<View
|
|
92
88
|
style={[
|
|
@@ -113,7 +109,7 @@ export const GenericModal: React.FC<GenericModalProps> = ({
|
|
|
113
109
|
<AtomicIcon
|
|
114
110
|
name={icon}
|
|
115
111
|
size="xl"
|
|
116
|
-
color={iconColor || 'primary'}
|
|
112
|
+
color={iconColor as any || 'primary'}
|
|
117
113
|
/>
|
|
118
114
|
</View>
|
|
119
115
|
)}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import React, { useMemo } from 'react';
|
|
23
|
-
import { View, StyleSheet
|
|
23
|
+
import { View, StyleSheet } from 'react-native';
|
|
24
24
|
import { ScreenLayout } from '@umituz/react-native-design-system/layouts';
|
|
25
25
|
import {
|
|
26
26
|
NavigationHeader,
|
|
@@ -88,11 +88,6 @@ export function GenericScreen<T>({
|
|
|
88
88
|
<NavigationHeader
|
|
89
89
|
title={header?.title || ''}
|
|
90
90
|
onBackPress={showBackButton ? onBackPress : undefined}
|
|
91
|
-
actions={header?.actions?.map((action) => ({
|
|
92
|
-
icon: action.icon,
|
|
93
|
-
onPress: action.onPress,
|
|
94
|
-
testID: action.testID,
|
|
95
|
-
}))}
|
|
96
91
|
/>
|
|
97
92
|
) : undefined;
|
|
98
93
|
|
|
@@ -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;
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useDisclaimerModal Hook
|
|
3
|
-
*
|
|
4
|
-
* Refactored to use GenericModal and ModalConfig.
|
|
5
|
-
* Replaces old DisclaimerModal component.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```ts
|
|
9
|
-
* const { DisclaimerModal, showDisclaimer } = useDisclaimerModal();
|
|
10
|
-
*
|
|
11
|
-
* return (
|
|
12
|
-
* <>
|
|
13
|
-
* <Button onPress={() => showDisclaimer({ title: 'Terms', content: '...' })} />
|
|
14
|
-
* <DisclaimerModal />
|
|
15
|
-
* </>
|
|
16
|
-
* );
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { useCallback } from 'react';
|
|
21
|
-
import { GenericModal } from '../../../../presentation/components/GenericModal';
|
|
22
|
-
import { useModalState } from '../../../../core/patterns/Modal/useModalState';
|
|
23
|
-
import type { ModalConfig } from '../../../../core/patterns/Modal/ModalConfig';
|
|
24
|
-
|
|
25
|
-
export interface DisclaimerModalOptions {
|
|
26
|
-
/**
|
|
27
|
-
* Disclaimer title
|
|
28
|
-
*/
|
|
29
|
-
title: string;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Disclaimer content
|
|
33
|
-
*/
|
|
34
|
-
content: string;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Custom modal configuration
|
|
38
|
-
*/
|
|
39
|
-
config?: Partial<ModalConfig>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Disclaimer modal hook
|
|
44
|
-
*/
|
|
45
|
-
export function useDisclaimerModal() {
|
|
46
|
-
const modal = useModalState();
|
|
47
|
-
|
|
48
|
-
const showDisclaimer = useCallback((options: DisclaimerModalOptions) => {
|
|
49
|
-
const { title, content, config: customConfig } = options;
|
|
50
|
-
|
|
51
|
-
const modalConfig: ModalConfig = {
|
|
52
|
-
title,
|
|
53
|
-
message: content,
|
|
54
|
-
dismissible: true,
|
|
55
|
-
closeOnBackdropPress: true,
|
|
56
|
-
closeOnBackPress: true,
|
|
57
|
-
...customConfig,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
modal.show(modalConfig);
|
|
61
|
-
}, [modal]);
|
|
62
|
-
|
|
63
|
-
const DisclaimerModal = useCallback(() => {
|
|
64
|
-
return <GenericModal state={modal} testID="disclaimer-modal" />;
|
|
65
|
-
}, [modal]);
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
DisclaimerModal,
|
|
69
|
-
showDisclaimer,
|
|
70
|
-
modal,
|
|
71
|
-
};
|
|
72
|
-
}
|