@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.
Files changed (24) hide show
  1. package/package.json +1 -1
  2. package/src/core/index.ts +1 -1
  3. package/src/core/patterns/Screen/useScreenData.ts +0 -1
  4. package/src/core/utils/logger.ts +5 -5
  5. package/src/domains/disclaimer/index.ts +0 -3
  6. package/src/domains/disclaimer/presentation/components/DisclaimerSetting.tsx +18 -43
  7. package/src/domains/disclaimer/presentation/screens/DisclaimerScreen.tsx +42 -92
  8. package/src/domains/feedback/index.ts +2 -2
  9. package/src/domains/feedback/presentation/components/SupportSection.tsx +16 -44
  10. package/src/domains/feedback/presentation/screens/FeatureRequestScreen.tsx +13 -35
  11. package/src/domains/feedback/presentation/screens/FeedbackScreen.tsx +75 -0
  12. package/src/domains/notifications/infrastructure/services/NotificationService.ts +2 -4
  13. package/src/domains/rating/application/services/RatingService.ts +31 -0
  14. package/src/domains/rating/index.ts +3 -3
  15. package/src/domains/rating/presentation/hooks/useAppRating.tsx +42 -65
  16. package/src/domains/rating/presentation/screens/RatingPromptScreen.tsx +162 -0
  17. package/src/infrastructure/services/SettingsService.ts +3 -9
  18. package/src/presentation/components/GenericModal.tsx +3 -7
  19. package/src/presentation/components/GenericScreen.tsx +1 -6
  20. package/src/presentation/navigation/hooks/useSettingsScreens.ts +26 -1
  21. package/src/presentation/navigation/types.ts +6 -0
  22. package/src/domains/disclaimer/presentation/hooks/useDisclaimerModal.ts +0 -72
  23. package/src/domains/feedback/presentation/hooks/useFeedbackModal.ts +0 -182
  24. package/src/domains/rating/presentation/hooks/useRatingPromptModal.ts +0 -122
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-settings",
3
- "version": "5.4.10",
3
+ "version": "5.4.12",
4
4
  "description": "Complete settings hub for React Native apps - consolidated package with settings, localization, about, legal, appearance, feedback, FAQs, rating, and gamification - expo-store-review and expo-device now lazy loaded",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./dist/index.d.ts",
package/src/core/index.ts CHANGED
@@ -43,6 +43,7 @@ export { ModalPresets, confirmed, dismissed } from './patterns/Modal/ModalConfig
43
43
  // =============================================================================
44
44
 
45
45
  export { useScreenData, useSimpleScreenData } from './patterns/Screen/useScreenData';
46
+ export type { UseScreenDataOptions } from './patterns/Screen/useScreenData';
46
47
  export type {
47
48
  ScreenData,
48
49
  ScreenDataState,
@@ -54,7 +55,6 @@ export type {
54
55
  ScreenError,
55
56
  ScreenEmpty,
56
57
  ScreenLayout,
57
- UseScreenDataOptions,
58
58
  } from './patterns/Screen/ScreenConfig';
59
59
 
60
60
  export { ScreenPresets } from './patterns/Screen/ScreenConfig';
@@ -21,7 +21,6 @@ import { useCallback, useEffect, useRef, useState } from 'react';
21
21
  import type {
22
22
  ScreenData,
23
23
  ScreenDataState,
24
- ScreenDataActions,
25
24
  ScreenFetchFunction,
26
25
  } from './ScreenConfig';
27
26
 
@@ -17,7 +17,7 @@ import { isDev } from '../../utils/devUtils';
17
17
 
18
18
  export type LogLevel = 'info' | 'warn' | 'error' | 'debug';
19
19
 
20
- interface LogContext {
20
+ export interface LogContext {
21
21
  component?: string;
22
22
  service?: string;
23
23
  domain?: string;
@@ -39,7 +39,7 @@ export class Logger {
39
39
  */
40
40
  info(message: string, ...args: unknown[]): void {
41
41
  if (isDev()) {
42
- const prefix = this.formatPrefix('INFO');
42
+ const prefix = this.formatPrefix('info');
43
43
  console.log(prefix, message, ...args);
44
44
  }
45
45
  }
@@ -49,7 +49,7 @@ export class Logger {
49
49
  */
50
50
  warn(message: string, ...args: unknown[]): void {
51
51
  if (isDev()) {
52
- const prefix = this.formatPrefix('WARN');
52
+ const prefix = this.formatPrefix('warn');
53
53
  console.warn(prefix, message, ...args);
54
54
  }
55
55
  }
@@ -59,7 +59,7 @@ export class Logger {
59
59
  */
60
60
  error(message: string, error?: unknown, ...args: unknown[]): void {
61
61
  if (isDev()) {
62
- const prefix = this.formatPrefix('ERROR');
62
+ const prefix = this.formatPrefix('error');
63
63
  console.error(prefix, message, error, ...args);
64
64
  }
65
65
  }
@@ -69,7 +69,7 @@ export class Logger {
69
69
  */
70
70
  debug(message: string, ...args: unknown[]): void {
71
71
  if (isDev()) {
72
- const prefix = this.formatPrefix('DEBUG');
72
+ const prefix = this.formatPrefix('debug');
73
73
  console.log(prefix, message, ...args);
74
74
  }
75
75
  }
@@ -18,9 +18,6 @@ export type { DisclaimerSettingProps } from './presentation/components/Disclaime
18
18
  export { DisclaimerCard } from './presentation/components/DisclaimerCard';
19
19
  export type { DisclaimerCardProps } from './presentation/components/DisclaimerCard';
20
20
 
21
- export { useDisclaimerModal } from './presentation/hooks/useDisclaimerModal';
22
- export type { DisclaimerModalOptions } from './presentation/hooks/useDisclaimerModal';
23
-
24
21
  // =============================================================================
25
22
  // PRESENTATION LAYER - Screens
26
23
  // =============================================================================
@@ -5,10 +5,9 @@
5
5
  * Used in About screens for apps that require disclaimers
6
6
  *
7
7
  * Features:
8
- * - Tappable card that opens full disclaimer modal
8
+ * - Tappable card that opens full disclaimer screen
9
9
  * - Warning icon with background color
10
10
  * - Internationalized title and message
11
- * - Full-screen modal with scrollable content
12
11
  * - NO shadows (CLAUDE.md compliance)
13
12
  * - Universal across iOS, Android, Web (NO Platform.OS checks)
14
13
  *
@@ -17,12 +16,11 @@
17
16
  * - Requires translations: settings.disclaimer.title, settings.disclaimer.message, settings.disclaimer.shortMessage
18
17
  */
19
18
 
20
- import React, { useState, useEffect, useCallback } from 'react';
21
- import { Modal } from 'react-native';
19
+ import React, { useCallback } from 'react';
22
20
 
23
21
  import { useAppDesignTokens, withAlpha } from '@umituz/react-native-design-system/theme';
22
+ import { useAppNavigation } from '@umituz/react-native-design-system/molecules';
24
23
  import { DisclaimerCard } from './DisclaimerCard';
25
- import { DisclaimerModal } from './DisclaimerModal';
26
24
 
27
25
  export interface DisclaimerSettingProps {
28
26
  /** Custom title */
@@ -48,50 +46,27 @@ export const DisclaimerSetting: React.FC<DisclaimerSettingProps> = ({
48
46
  backgroundColor,
49
47
  }) => {
50
48
  const tokens = useAppDesignTokens();
51
- const [modalVisible, setModalVisible] = useState(false);
52
-
53
- useEffect(() => {
54
- return () => {
55
- setModalVisible(false);
56
- };
57
- }, []);
49
+ const navigation = useAppNavigation();
58
50
 
59
51
  const finalIconColor = iconColor || tokens.colors.warning;
60
52
  const finalBackgroundColor = backgroundColor || withAlpha(finalIconColor, 0.1);
61
53
 
62
- const handleOpenModal = useCallback(() => {
63
- setModalVisible(true);
64
- }, []);
65
-
66
- const handleCloseModal = useCallback(() => {
67
- setModalVisible(false);
68
- }, []);
54
+ const handleOpenDisclaimer = useCallback(() => {
55
+ navigation.push('Disclaimer' as never, {
56
+ title,
57
+ content,
58
+ });
59
+ }, [navigation, title, content]);
69
60
 
70
61
  return (
71
- <>
72
- <DisclaimerCard
73
- title={title}
74
- shortMessage={shortMessage}
75
- iconName={iconName}
76
- iconColor={finalIconColor}
77
- backgroundColor={finalBackgroundColor}
78
- onPress={handleOpenModal}
79
- />
80
-
81
- <Modal
82
- visible={modalVisible}
83
- animationType="none"
84
- presentationStyle="pageSheet"
85
- onRequestClose={handleCloseModal}
86
- >
87
- <DisclaimerModal
88
- visible={modalVisible}
89
- title={title}
90
- content={content}
91
- onClose={handleCloseModal}
92
- />
93
- </Modal>
94
- </>
62
+ <DisclaimerCard
63
+ title={title}
64
+ shortMessage={shortMessage}
65
+ iconName={iconName}
66
+ iconColor={finalIconColor}
67
+ backgroundColor={finalBackgroundColor}
68
+ onPress={handleOpenDisclaimer}
69
+ />
95
70
  );
96
71
  };
97
72
 
@@ -1,115 +1,65 @@
1
1
  /**
2
- * DisclaimerScreen Component
2
+ * Disclaimer Screen
3
3
  *
4
- * Full-screen disclaimer display for navigation-based usage
5
- * Can be registered as a screen in navigation stack
6
- *
7
- * Features:
8
- * - SafeAreaView wrapper for proper display
9
- * - Scrollable content for long disclaimers
10
- * - Customizable title and content via props or translations
11
- * - NO shadows (CLAUDE.md compliance)
12
- * - Universal across iOS, Android, Web
4
+ * Full screen for displaying disclaimer/important legal notice.
5
+ * Replaces modal approach with native navigation.
13
6
  */
14
7
 
15
8
  import React from 'react';
16
- import { View, StyleSheet } from 'react-native';
17
- import { AtomicText, AtomicIcon, type IconName } from '@umituz/react-native-design-system/atoms';
9
+ import { View, StyleSheet, ScrollView } from 'react-native';
18
10
  import { ScreenLayout } from '@umituz/react-native-design-system/layouts';
19
- import { useAppNavigation, NavigationHeader } from '@umituz/react-native-design-system/molecules';
20
- import { useAppDesignTokens, withAlpha } from '@umituz/react-native-design-system/theme';
11
+ import { AtomicText } from '@umituz/react-native-design-system/atoms';
12
+ import { NavigationHeader, useAppNavigation } from '@umituz/react-native-design-system/molecules';
21
13
 
22
- export interface DisclaimerScreenProps {
23
- /** Custom title (overrides translation) */
24
- title?: string;
25
- /** Custom title translation key */
26
- titleKey?: string;
27
- /** Custom content (overrides translation) */
28
- content?: string;
29
- /** Custom content translation key */
30
- contentKey?: string;
31
- /** Custom icon name */
32
- iconName?: string;
14
+ export interface DisclaimerScreenParams {
15
+ title: string;
16
+ content: string;
17
+ [key: string]: unknown;
33
18
  }
34
19
 
35
- export const DisclaimerScreen: React.FC<DisclaimerScreenProps> = ({
36
- title,
37
- content,
38
- iconName = 'alert-triangle',
39
- }) => {
40
- const tokens = useAppDesignTokens();
41
- const styles = getStyles(tokens);
42
-
43
- const displayTitle = title || "";
44
- const displayContent = content || "";
20
+ export interface DisclaimerScreenProps {
21
+ route: {
22
+ params: DisclaimerScreenParams;
23
+ };
24
+ }
45
25
 
26
+ export const DisclaimerScreen: React.FC<DisclaimerScreenProps> = ({ route }) => {
46
27
  const navigation = useAppNavigation();
28
+ const { title, content } = route.params;
47
29
 
48
30
  return (
49
31
  <ScreenLayout
50
32
  scrollable={true}
51
33
  edges={['top', 'bottom', 'left', 'right']}
52
- contentContainerStyle={styles.scrollContent}
53
34
  hideScrollIndicator={false}
54
- header={
55
- <NavigationHeader
56
- title={displayTitle}
57
- onBackPress={() => navigation.goBack()}
58
- />
59
- }
60
35
  >
61
- {/* Icon Header */}
62
- <View style={styles.iconHeader}>
63
- <View
64
- style={[
65
- styles.iconContainer,
66
- {
67
- backgroundColor: withAlpha(tokens.colors.warning, 0.1),
68
- borderColor: withAlpha(tokens.colors.warning, 0.3),
69
- },
70
- ]}
71
- >
72
- <AtomicIcon name={iconName as IconName} color="warning" size="xl" />
73
- </View>
36
+ <NavigationHeader
37
+ title={title || 'Disclaimer'}
38
+ onBackPress={() => navigation.goBack()}
39
+ />
40
+ <View style={styles.container}>
41
+ <ScrollView style={styles.scrollView} contentContainerStyle={styles.scrollContent}>
42
+ <AtomicText style={styles.content}>{content}</AtomicText>
43
+ </ScrollView>
74
44
  </View>
75
-
76
- {/* Title */}
77
- <AtomicText type="headlineMedium" color="primary" style={styles.title}>
78
- {displayTitle}
79
- </AtomicText>
80
-
81
- {/* Content */}
82
- <AtomicText type="bodyMedium" color="secondary" style={styles.content}>
83
- {displayContent}
84
- </AtomicText>
85
45
  </ScreenLayout>
86
46
  );
87
47
  };
88
48
 
89
- const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
90
- StyleSheet.create({
91
- scrollContent: {
92
- padding: tokens.spacing.lg,
93
- paddingTop: tokens.spacing.xl,
94
- },
95
- iconHeader: {
96
- alignItems: 'center',
97
- marginBottom: tokens.spacing.lg,
98
- },
99
- iconContainer: {
100
- width: 72,
101
- height: 72,
102
- borderRadius: 36,
103
- alignItems: 'center',
104
- justifyContent: 'center',
105
- borderWidth: 2,
106
- },
107
- title: {
108
- textAlign: 'center',
109
- marginBottom: tokens.spacing.lg,
110
- },
111
- content: {
112
- lineHeight: 24,
113
- textAlign: 'left',
114
- },
115
- });
49
+ const styles = StyleSheet.create({
50
+ container: {
51
+ flex: 1,
52
+ },
53
+ scrollView: {
54
+ flex: 1,
55
+ },
56
+ scrollContent: {
57
+ padding: 20,
58
+ paddingBottom: 40,
59
+ },
60
+ content: {
61
+ fontSize: 16,
62
+ lineHeight: 24,
63
+ color: 'rgba(255, 255, 255, 0.9)',
64
+ },
65
+ });
@@ -4,10 +4,10 @@
4
4
  */
5
5
 
6
6
  export * from './presentation/components/FeedbackForm';
7
- export { useFeedbackModal } from './presentation/hooks/useFeedbackModal';
8
- export type { FeedbackModalOptions } from './presentation/hooks/useFeedbackModal';
9
7
  export { SupportSection } from './presentation/components/SupportSection';
10
8
  export type { SupportSectionProps, FeedbackModalTexts } from './presentation/components/SupportSection';
11
9
  export * from './presentation/hooks/useFeedbackForm';
12
10
  export * from './domain/entities/FeedbackEntity';
13
11
  export * from './domain/entities/FeatureRequestEntity';
12
+ export { FeedbackScreen } from './presentation/screens/FeedbackScreen';
13
+ export type { FeedbackScreenProps, FeedbackScreenParams } from './presentation/screens/FeedbackScreen';
@@ -4,11 +4,10 @@
4
4
  * Agnostic of UI implementation via render props
5
5
  */
6
6
 
7
- import React, { useState, useCallback } from "react";
7
+ import React, { useCallback } from "react";
8
8
  import { Linking } from "react-native";
9
- import { FeedbackModal } from "./FeedbackModal";
9
+ import { useAppNavigation } from "@umituz/react-native-design-system/molecules";
10
10
  import type { FeedbackType } from "../../domain/entities/FeedbackEntity";
11
- import { isDev } from "../../../../utils/devUtils";
12
11
 
13
12
  export interface FeedbackConfig {
14
13
  enabled?: boolean;
@@ -47,7 +46,7 @@ export interface SupportSectionProps {
47
46
  onPress: () => void;
48
47
  isLast?: boolean
49
48
  }) => React.ReactElement | null;
50
- /** Texts for the feedback modal */
49
+ /** Texts for the feedback screen */
51
50
  feedbackModalTexts?: FeedbackModalTexts;
52
51
  }
53
52
 
@@ -58,27 +57,19 @@ export const SupportSection: React.FC<SupportSectionProps> = ({
58
57
  renderItem,
59
58
  feedbackModalTexts
60
59
  }) => {
61
- const [modalVisible, setModalVisible] = useState(false);
62
- const [isSubmitting, setIsSubmitting] = useState(false);
63
-
64
- const handleFeedbackSubmit = async (data: { type: FeedbackType; rating: number; description: string; title: string }) => {
65
- if (feedbackConfig.config?.onSubmit) {
66
- setIsSubmitting(true);
67
- try {
68
- await feedbackConfig.config.onSubmit(data);
69
- setModalVisible(false);
70
- } catch (error) {
71
- if (isDev()) {
72
- console.error('[SupportSection] Failed to submit feedback:', error);
73
- }
74
- setModalVisible(false);
75
- } finally {
76
- setIsSubmitting(false);
77
- }
78
- } else {
79
- setModalVisible(false);
60
+ const navigation = useAppNavigation();
61
+
62
+ const handleFeedbackPress = useCallback(() => {
63
+ if (feedbackConfig.config?.onPress) {
64
+ feedbackConfig.config.onPress();
65
+ } else if (feedbackModalTexts) {
66
+ navigation.push('Feedback' as never, {
67
+ initialType: feedbackConfig.config?.initialType,
68
+ title: feedbackModalTexts.title,
69
+ texts: feedbackModalTexts,
70
+ });
80
71
  }
81
- };
72
+ }, [navigation, feedbackConfig.config, feedbackModalTexts]);
82
73
 
83
74
  const handleRateApp = useCallback(async () => {
84
75
  const config = ratingConfig.config;
@@ -122,7 +113,7 @@ export const SupportSection: React.FC<SupportSectionProps> = ({
122
113
  {showFeedback && feedbackConfig.config?.description && renderItem({
123
114
  title: feedbackConfig.config.description,
124
115
  icon: "mail",
125
- onPress: feedbackConfig.config.onPress || (() => setModalVisible(true)),
116
+ onPress: handleFeedbackPress,
126
117
  isLast: !showRating
127
118
  })}
128
119
 
@@ -135,25 +126,6 @@ export const SupportSection: React.FC<SupportSectionProps> = ({
135
126
  </>
136
127
  )
137
128
  })}
138
-
139
- {showFeedback && feedbackModalTexts && (
140
- <FeedbackModal
141
- visible={modalVisible}
142
- onClose={() => setModalVisible(false)}
143
- onSubmit={handleFeedbackSubmit}
144
- initialType={feedbackConfig.config?.initialType}
145
- isSubmitting={isSubmitting}
146
- title={feedbackModalTexts.title}
147
- texts={{
148
- ratingLabel: feedbackModalTexts.ratingLabel,
149
- descriptionPlaceholder: feedbackModalTexts.descriptionPlaceholder,
150
- submitButton: feedbackModalTexts.submitButton,
151
- submittingButton: feedbackModalTexts.submittingButton,
152
- feedbackTypes: feedbackModalTexts.feedbackTypes,
153
- defaultTitle: feedbackModalTexts.defaultTitle,
154
- }}
155
- />
156
- )}
157
129
  </>
158
130
  );
159
131
  };
@@ -11,12 +11,12 @@ import {
11
11
  AtomicIcon,
12
12
  } from "@umituz/react-native-design-system/atoms";
13
13
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
14
+ import { useAppNavigation } from "@umituz/react-native-design-system/molecules";
14
15
  import { devWarn } from "../../../../utils/devUtils";
15
16
  import { ScreenLayout } from "@umituz/react-native-design-system/layouts";
16
- import { FeedbackModal } from "../components/FeedbackModal";
17
17
  import { ICON_PATHS } from "../../../../utils/iconPaths";
18
18
  import { useFeatureRequests } from "../../infrastructure/useFeatureRequests";
19
- import type { FeedbackRating } from "../../domain/entities/FeedbackEntity";
19
+ import type { FeedbackType } from "../../domain/entities/FeedbackEntity";
20
20
  import type { FeatureRequestItem } from "../../domain/entities/FeatureRequestEntity";
21
21
  import type { FeedbackFormTexts } from "../components/FeedbackFormProps";
22
22
 
@@ -29,11 +29,10 @@ interface FeatureRequestScreenProps {
29
29
 
30
30
  export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ config, texts }) => {
31
31
  const tokens = useAppDesignTokens();
32
+ const navigation = useAppNavigation();
32
33
  const { requests, userVotes, isLoading, vote, submitRequest, userId } = useFeatureRequests();
33
34
 
34
35
  const [activeTab, setActiveTab] = useState<'all' | 'my' | 'roadmap'>('all');
35
- const [isModalVisible, setIsModalVisible] = useState(false);
36
- const [isSubmitting, setIsSubmitting] = useState(false);
37
36
 
38
37
  const t = config?.translations || {};
39
38
  const screenTitle = t.screen_title || "Feedback & Features";
@@ -56,23 +55,6 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
56
55
  dismissed: t.status?.dismissed || "Dismissed",
57
56
  };
58
57
 
59
- const handleSubmit = useCallback(async (data: { title?: string; description: string; type?: string; rating?: FeedbackRating }) => {
60
- setIsSubmitting(true);
61
- try {
62
- await submitRequest({
63
- title: data.title || "New Request",
64
- description: data.description,
65
- type: data.type || "feature_request",
66
- rating: data.rating,
67
- });
68
- setIsModalVisible(false);
69
- } catch (error) {
70
- devWarn("[FeatureRequestScreen] Submit failed:", error);
71
- } finally {
72
- setIsSubmitting(false);
73
- }
74
- }, [submitRequest]);
75
-
76
58
  const getStatusColor = (status: string) => {
77
59
  switch (status) {
78
60
  case 'planned': return '#3b82f6';
@@ -154,7 +136,11 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
154
136
  <AtomicText style={styles.headerTitle}>{screenTitle}</AtomicText>
155
137
  <TouchableOpacity
156
138
  style={[styles.addButton, { backgroundColor: tokens.colors.primary }]}
157
- onPress={() => setIsModalVisible(true)}
139
+ onPress={() => navigation.push('Feedback' as never, {
140
+ initialType: 'feature_request' as FeedbackType,
141
+ title: texts?.title,
142
+ texts: texts,
143
+ })}
158
144
  >
159
145
  <AtomicIcon
160
146
  svgPath={ICON_PATHS['plus']}
@@ -236,22 +222,14 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
236
222
  windowSize={5}
237
223
  />
238
224
 
239
- {isModalVisible && (
240
- <FeedbackModal
241
- visible={isModalVisible}
242
- onClose={() => setIsModalVisible(false)}
243
- title={texts?.title}
244
- onSubmit={handleSubmit}
245
- texts={texts}
246
- initialType="feature_request"
247
- isSubmitting={isSubmitting}
248
- />
249
- )}
250
-
251
225
  <View style={styles.floatingHint}>
252
226
  <TouchableOpacity
253
227
  style={[styles.hintBadge, { backgroundColor: tokens.colors.primary }]}
254
- onPress={() => setIsModalVisible(true)}
228
+ onPress={() => navigation.push('Feedback' as never, {
229
+ initialType: 'feature_request' as FeedbackType,
230
+ title: texts?.title,
231
+ texts: texts,
232
+ })}
255
233
  >
256
234
  <AtomicText style={styles.hintText}>{newIdeaLabel}</AtomicText>
257
235
  </TouchableOpacity>
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Feedback Screen
3
+ *
4
+ * Full screen for submitting feedback.
5
+ * Replaces modal approach with native navigation.
6
+ */
7
+
8
+ import React from 'react';
9
+ import { StyleSheet } from 'react-native';
10
+ import { ScreenLayout } from '@umituz/react-native-design-system/layouts';
11
+ import { NavigationHeader } from '@umituz/react-native-design-system/molecules';
12
+ import { FeedbackForm } from '../components/FeedbackForm';
13
+ import type { FeedbackType } from '../../domain/entities/FeedbackEntity';
14
+ import type { FeedbackFormProps } from '../components/FeedbackFormProps';
15
+ import { useAppNavigation } from '@umituz/react-native-design-system/molecules';
16
+
17
+ export interface FeedbackScreenParams {
18
+ initialType?: FeedbackType;
19
+ title?: string;
20
+ texts: FeedbackFormProps['texts'];
21
+ [key: string]: unknown;
22
+ }
23
+
24
+ export interface FeedbackScreenProps {
25
+ route: {
26
+ params: FeedbackScreenParams;
27
+ };
28
+ }
29
+
30
+ export const FeedbackScreen: React.FC<FeedbackScreenProps> = ({ route }) => {
31
+ const navigation = useAppNavigation();
32
+ const { initialType, title, texts } = route.params;
33
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
34
+
35
+ const handleSubmit = async (_data: { type: FeedbackType; rating: number; description: string; title: string }) => {
36
+ setIsSubmitting(true);
37
+ try {
38
+ // Navigate back with result
39
+ navigation.goBack();
40
+ // Note: In a real app, you'd emit an event or call a callback here
41
+ // For now, we'll just close the screen
42
+ } catch (error) {
43
+ console.error('[FeedbackScreen] Submit failed:', error);
44
+ } finally {
45
+ setIsSubmitting(false);
46
+ }
47
+ };
48
+
49
+ return (
50
+ <ScreenLayout
51
+ scrollable={true}
52
+ edges={['top', 'bottom', 'left', 'right']}
53
+ keyboardAvoiding={true}
54
+ hideScrollIndicator={false}
55
+ contentContainerStyle={styles.content}
56
+ >
57
+ <NavigationHeader
58
+ title={title || 'Feedback'}
59
+ onBackPress={() => navigation.goBack()}
60
+ />
61
+ <FeedbackForm
62
+ onSubmit={handleSubmit}
63
+ initialType={initialType}
64
+ isSubmitting={isSubmitting}
65
+ texts={texts}
66
+ />
67
+ </ScreenLayout>
68
+ );
69
+ };
70
+
71
+ const styles = StyleSheet.create({
72
+ content: {
73
+ padding: 20,
74
+ },
75
+ });
@@ -31,10 +31,8 @@ export class NotificationService extends BaseService {
31
31
 
32
32
  private ensureConfigured() {
33
33
  if (!this.isConfigured) {
34
- this.executeUnsafe('ensureConfigured', () => {
35
- NotificationManager.configure();
36
- this.isConfigured = true;
37
- });
34
+ NotificationManager.configure();
35
+ this.isConfigured = true;
38
36
  }
39
37
  }
40
38
 
@@ -149,3 +149,34 @@ export function getRatingService(): RatingService {
149
149
  * Default export for backward compatibility
150
150
  */
151
151
  export const ratingService = getRatingService();
152
+
153
+ /**
154
+ * Static convenience functions that delegate to singleton
155
+ */
156
+ export async function trackEvent(eventType: string): Promise<void> {
157
+ await ratingService.trackEvent(eventType);
158
+ }
159
+
160
+ export async function shouldShowPrompt(config: RatingConfig): Promise<boolean> {
161
+ return await ratingService.shouldShowPrompt(config);
162
+ }
163
+
164
+ export async function markPromptShown(eventType: string): Promise<void> {
165
+ await ratingService.markPromptShown(eventType);
166
+ }
167
+
168
+ export async function markRated(): Promise<void> {
169
+ await ratingService.markRated();
170
+ }
171
+
172
+ export async function markDismissed(): Promise<void> {
173
+ await ratingService.markDismissed();
174
+ }
175
+
176
+ export async function getState(eventType: string): Promise<RatingState> {
177
+ return await ratingService.getState(eventType);
178
+ }
179
+
180
+ export async function reset(eventType?: string): Promise<void> {
181
+ await ratingService.reset(eventType);
182
+ }