@umituz/react-native-settings 5.4.11 → 5.4.13

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-settings",
3
- "version": "5.4.11",
3
+ "version": "5.4.13",
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",
@@ -197,6 +197,10 @@ export const AppearanceScreen: React.FC<AppearanceScreenProps> = ({
197
197
  contentContainerStyle={{
198
198
  paddingBottom: tokens.spacing['2xl'],
199
199
  }}
200
+ removeClippedSubviews={true}
201
+ maxToRenderPerBatch={5}
202
+ windowSize={3}
203
+ initialNumToRender={3}
200
204
  />
201
205
  </ScreenLayout>
202
206
  );
@@ -4,7 +4,7 @@
4
4
  * Uses design system tokens for theming
5
5
  */
6
6
 
7
- import React, { useMemo } from 'react';
7
+ import React, { useMemo, useCallback } from 'react';
8
8
  import { View, StyleSheet, ViewStyle, TextStyle, useWindowDimensions, FlatList } from 'react-native';
9
9
  import { getContentMaxWidth } from '@umituz/react-native-design-system/device';
10
10
  import { ScreenLayout } from '@umituz/react-native-design-system/layouts';
@@ -102,7 +102,7 @@ export const FAQScreen: React.FC<FAQScreenProps> = ({
102
102
  </View>
103
103
  ), [searchQuery, hasResults, searchPlaceholder, emptySearchTitle, emptySearchMessage, customStyles, tokens, contentMaxWidth]);
104
104
 
105
- const renderCategory = ({ item }: { item: FAQCategory }) => (
105
+ const renderCategory = useCallback(({ item }: { item: FAQCategory }) => (
106
106
  <View style={{ alignSelf: 'center', width: '100%', maxWidth: contentMaxWidth }}>
107
107
  <FAQCategoryComponent
108
108
  category={item}
@@ -111,7 +111,7 @@ export const FAQScreen: React.FC<FAQScreenProps> = ({
111
111
  styles={customStyles?.category}
112
112
  />
113
113
  </View>
114
- );
114
+ ), [contentMaxWidth, isExpanded, toggleExpansion, customStyles?.category]);
115
115
 
116
116
  return (
117
117
  <ScreenLayout
@@ -128,6 +128,10 @@ export const FAQScreen: React.FC<FAQScreenProps> = ({
128
128
  ListFooterComponent={<View style={styles.footer} />}
129
129
  showsVerticalScrollIndicator={false}
130
130
  contentContainerStyle={{ paddingBottom: tokens.spacing.xl }}
131
+ removeClippedSubviews={true}
132
+ maxToRenderPerBatch={10}
133
+ windowSize={5}
134
+ initialNumToRender={8}
131
135
  />
132
136
  </ScreenLayout>
133
137
  );
@@ -8,7 +8,6 @@ import React, { useCallback } from "react";
8
8
  import { Linking } from "react-native";
9
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;
@@ -11,12 +11,11 @@ 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 { devWarn } from "../../../../utils/devUtils";
14
+ import { useAppNavigation } from "@umituz/react-native-design-system/molecules";
15
15
  import { ScreenLayout } from "@umituz/react-native-design-system/layouts";
16
- import { FeedbackModal } from "../components/FeedbackModal";
17
16
  import { ICON_PATHS } from "../../../../utils/iconPaths";
18
17
  import { useFeatureRequests } from "../../infrastructure/useFeatureRequests";
19
- import type { FeedbackType, FeedbackRating } from "../../domain/entities/FeedbackEntity";
18
+ import type { FeedbackType } from "../../domain/entities/FeedbackEntity";
20
19
  import type { FeatureRequestItem } from "../../domain/entities/FeatureRequestEntity";
21
20
  import type { FeedbackFormTexts } from "../components/FeedbackFormProps";
22
21
 
@@ -29,11 +28,10 @@ interface FeatureRequestScreenProps {
29
28
 
30
29
  export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ config, texts }) => {
31
30
  const tokens = useAppDesignTokens();
32
- const { requests, userVotes, isLoading, vote, submitRequest, userId } = useFeatureRequests();
31
+ const navigation = useAppNavigation();
32
+ const { requests, userVotes, isLoading, vote, submitRequest: _submitRequest, userId } = useFeatureRequests();
33
33
 
34
34
  const [activeTab, setActiveTab] = useState<'all' | 'my' | 'roadmap'>('all');
35
- const [isModalVisible, setIsModalVisible] = useState(false);
36
- const [isSubmitting, setIsSubmitting] = useState(false);
37
35
 
38
36
  const t = config?.translations || {};
39
37
  const screenTitle = t.screen_title || "Feedback & Features";
@@ -42,38 +40,21 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
42
40
  const bannerSub = t.banner?.subtitle || `${requests.length} feature requests`;
43
41
  const newIdeaLabel = t.new_idea || "NEW IDEA?";
44
42
 
45
- const tabLabels = {
43
+ const tabLabels = useMemo(() => ({
46
44
  all: t.tabs?.all || "All Requests",
47
45
  my: t.tabs?.my || "My Feedback",
48
46
  roadmap: t.tabs?.roadmap || "Roadmap",
49
- };
47
+ }), [t.tabs?.all, t.tabs?.my, t.tabs?.roadmap]);
50
48
 
51
- const statusLabels: Record<string, string> = {
49
+ const statusLabels = useMemo(() => ({
52
50
  planned: t.status?.planned || "Planned",
53
51
  review: t.status?.review || "Under Review",
54
52
  completed: t.status?.completed || "Completed",
55
53
  pending: t.status?.pending || "Pending",
56
54
  dismissed: t.status?.dismissed || "Dismissed",
57
- };
58
-
59
- const handleSubmit = useCallback(async (data: { title?: string; description: string; type?: string; rating?: number }) => {
60
- setIsSubmitting(true);
61
- try {
62
- await submitRequest({
63
- title: data.title || "New Request",
64
- description: data.description,
65
- type: (data.type || "feature_request") as FeedbackType,
66
- rating: data.rating as FeedbackRating,
67
- });
68
- setIsModalVisible(false);
69
- } catch (error) {
70
- devWarn("[FeatureRequestScreen] Submit failed:", error);
71
- } finally {
72
- setIsSubmitting(false);
73
- }
74
- }, [submitRequest]);
55
+ }), [t.status?.planned, t.status?.review, t.status?.completed, t.status?.pending, t.status?.dismissed]);
75
56
 
76
- const getStatusColor = (status: string) => {
57
+ const getStatusColor = useCallback((status: string) => {
77
58
  switch (status) {
78
59
  case 'planned': return '#3b82f6';
79
60
  case 'review': return '#f59e0b';
@@ -82,7 +63,7 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
82
63
  case 'dismissed': return '#ef4444';
83
64
  default: return tokens.colors.textSecondary;
84
65
  }
85
- };
66
+ }, [tokens.colors.textSecondary]);
86
67
 
87
68
  const filteredRequests = useMemo(() => {
88
69
  switch (activeTab) {
@@ -145,7 +126,7 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
145
126
  </View>
146
127
  </View>
147
128
  );
148
- }, [userVotes, vote, tokens.colors, getStatusColor, statusLabels, t]);
129
+ }, [userVotes, vote, tokens.colors, getStatusColor, statusLabels, t.comment_count]);
149
130
 
150
131
  const tabs = useMemo(() => (['all', 'my', 'roadmap'] as const), []);
151
132
 
@@ -154,7 +135,11 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
154
135
  <AtomicText style={styles.headerTitle}>{screenTitle}</AtomicText>
155
136
  <TouchableOpacity
156
137
  style={[styles.addButton, { backgroundColor: tokens.colors.primary }]}
157
- onPress={() => setIsModalVisible(true)}
138
+ onPress={() => navigation.push('Feedback' as never, {
139
+ initialType: 'feature_request' as FeedbackType,
140
+ title: texts?.title,
141
+ texts: texts,
142
+ })}
158
143
  >
159
144
  <AtomicIcon
160
145
  svgPath={ICON_PATHS['plus']}
@@ -236,22 +221,14 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
236
221
  windowSize={5}
237
222
  />
238
223
 
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
224
  <View style={styles.floatingHint}>
252
225
  <TouchableOpacity
253
226
  style={[styles.hintBadge, { backgroundColor: tokens.colors.primary }]}
254
- onPress={() => setIsModalVisible(true)}
227
+ onPress={() => navigation.push('Feedback' as never, {
228
+ initialType: 'feature_request' as FeedbackType,
229
+ title: texts?.title,
230
+ texts: texts,
231
+ })}
255
232
  >
256
233
  <AtomicText style={styles.hintText}>{newIdeaLabel}</AtomicText>
257
234
  </TouchableOpacity>
@@ -35,7 +35,7 @@ interface LanguageItemProps {
35
35
  // SVG path for checkmark icon (works without external icon library)
36
36
  const CHECKMARK_PATH = "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z";
37
37
 
38
- export const LanguageItem: React.FC<LanguageItemProps> = ({
38
+ export const LanguageItem: React.FC<LanguageItemProps> = React.memo(({
39
39
  item,
40
40
  isSelected,
41
41
  onSelect,
@@ -135,5 +135,5 @@ export const LanguageItem: React.FC<LanguageItemProps> = ({
135
135
  )}
136
136
  </TouchableOpacity>
137
137
  );
138
- };
138
+ });
139
139
 
@@ -39,7 +39,7 @@ const getFrequencyIcon = (frequency: ReminderFrequency): string => {
39
39
  return icons[frequency] || 'notifications';
40
40
  };
41
41
 
42
- export const ReminderItem: React.FC<ReminderItemProps> = ({
42
+ export const ReminderItem: React.FC<ReminderItemProps> = React.memo(({
43
43
  reminder,
44
44
  translations,
45
45
  onToggle,
@@ -97,7 +97,7 @@ export const ReminderItem: React.FC<ReminderItemProps> = ({
97
97
  </View>
98
98
  </View>
99
99
  );
100
- };
100
+ });
101
101
 
102
102
  const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
103
103
  StyleSheet.create({
@@ -4,7 +4,7 @@
4
4
  * Lazy loads expo-store-review to reduce bundle size
5
5
  */
6
6
 
7
- import React, { useCallback, useMemo } from "react";
7
+ import { useCallback, useMemo } from "react";
8
8
  import type {
9
9
  RatingConfig,
10
10
  UseAppRatingResult,
@@ -3,31 +3,31 @@
3
3
  * Single Responsibility: Display individual video tutorial card
4
4
  */
5
5
 
6
- import React from "react";
6
+ import React, { useMemo } from "react";
7
7
  import { View, Image, StyleSheet, TouchableOpacity } from "react-native";
8
8
  import { AtomicText } from "@umituz/react-native-design-system/atoms";
9
9
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
10
10
  import type { VideoTutorial } from "../../types";
11
11
 
12
+ const formatDuration = (seconds: number): string => {
13
+ const minutes = Math.floor(seconds / 60);
14
+ const remainingSeconds = seconds % 60;
15
+ return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
16
+ };
17
+
12
18
  interface VideoTutorialCardProps {
13
19
  readonly tutorial: VideoTutorial;
14
20
  readonly onPress: () => void;
15
21
  readonly horizontal?: boolean;
16
22
  }
17
23
 
18
- export const VideoTutorialCard: React.FC<VideoTutorialCardProps> = ({
24
+ export const VideoTutorialCard: React.FC<VideoTutorialCardProps> = React.memo(({
19
25
  tutorial,
20
26
  onPress,
21
27
  horizontal = false,
22
28
  }) => {
23
29
  const tokens = useAppDesignTokens();
24
- const styles = getStyles(tokens);
25
-
26
- const formatDuration = (seconds: number): string => {
27
- const minutes = Math.floor(seconds / 60);
28
- const remainingSeconds = seconds % 60;
29
- return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
30
- };
30
+ const styles = useMemo(() => getStyles(tokens), [tokens]);
31
31
 
32
32
  return (
33
33
  <TouchableOpacity
@@ -84,7 +84,7 @@ export const VideoTutorialCard: React.FC<VideoTutorialCardProps> = ({
84
84
  </View>
85
85
  </TouchableOpacity>
86
86
  );
87
- };
87
+ });
88
88
 
89
89
  const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) => StyleSheet.create({
90
90
  container: { borderRadius: 12, borderWidth: 1, marginBottom: 12, overflow: "hidden" },