@umituz/react-native-settings 4.20.29 → 4.20.30

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": "4.20.29",
3
+ "version": "4.20.30",
4
4
  "description": "Complete settings hub for React Native apps - consolidated package with settings, about, legal, appearance, feedback, FAQs, and rating",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Cloud Sync Domain
3
+ * Cloud synchronization settings and status
4
+ */
5
+
6
+ export { CloudSyncSetting } from "./presentation/components/CloudSyncSetting";
7
+ export type { CloudSyncSettingProps } from "./presentation/components/CloudSyncSetting";
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import React, { useMemo } from "react";
7
- import { SettingItem } from "./SettingItem";
7
+ import { SettingsItemCard } from "../../../../presentation/components/SettingsItemCard";
8
8
 
9
9
  export interface CloudSyncSettingProps {
10
10
  title?: string;
@@ -13,7 +13,6 @@ export interface CloudSyncSettingProps {
13
13
  lastSynced?: Date | null;
14
14
  onPress?: () => void;
15
15
  disabled?: boolean;
16
- isLast?: boolean;
17
16
  }
18
17
 
19
18
  const formatLastSynced = (date: Date): string => {
@@ -42,7 +41,6 @@ export const CloudSyncSetting: React.FC<CloudSyncSettingProps> = ({
42
41
  lastSynced,
43
42
  onPress,
44
43
  disabled = false,
45
- isLast = false,
46
44
  }) => {
47
45
  const displayValue = useMemo(() => {
48
46
  if (isSyncing) {
@@ -58,13 +56,12 @@ export const CloudSyncSetting: React.FC<CloudSyncSettingProps> = ({
58
56
  }, [isSyncing, description, lastSynced]);
59
57
 
60
58
  return (
61
- <SettingItem
59
+ <SettingsItemCard
62
60
  icon="cloud"
63
61
  title={title}
64
- value={displayValue}
62
+ description={displayValue}
65
63
  onPress={onPress}
66
64
  disabled={disabled || isSyncing}
67
- isLast={isLast}
68
65
  />
69
66
  );
70
67
  };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Dev Domain
3
+ * Development-only settings and utilities
4
+ * Only visible in __DEV__ mode
5
+ */
6
+
7
+ export { DevSettingsSection } from "./presentation/components/DevSettingsSection";
8
+ export type { DevSettingsProps } from "./presentation/components/DevSettingsSection";
9
+
10
+ export { StorageClearSetting } from "./presentation/components/StorageClearSetting";
11
+ export type { StorageClearSettingProps } from "./presentation/components/StorageClearSetting";
@@ -11,8 +11,8 @@ import React from "react";
11
11
  import { Alert } from "react-native";
12
12
  import { useAppDesignTokens } from "@umituz/react-native-design-system";
13
13
  import { storageRepository } from "@umituz/react-native-storage";
14
- import { SettingsSection } from "./SettingsSection";
15
- import { SettingItem } from "./SettingItem";
14
+ import { SettingsSection } from "../../../../presentation/components/SettingsSection";
15
+ import { SettingsItemCard } from "../../../../presentation/components/SettingsItemCard";
16
16
 
17
17
  // Default texts (English only - DEV feature)
18
18
  const DEFAULT_TEXTS = {
@@ -100,14 +100,12 @@ export const DevSettingsSection: React.FC<DevSettingsProps> = ({
100
100
  return (
101
101
  <SettingsSection title={t.sectionTitle}>
102
102
  {customDevComponents.map((component) => component)}
103
- <SettingItem
103
+ <SettingsItemCard
104
104
  icon="trash-outline"
105
105
  title={t.clearTitle}
106
- value={t.clearDescription}
106
+ description={t.clearDescription}
107
107
  onPress={handleClearData}
108
108
  iconColor={tokens.colors.error}
109
- titleColor={tokens.colors.error}
110
- isLast={true}
111
109
  />
112
110
  </SettingsSection>
113
111
  );
@@ -5,15 +5,13 @@
5
5
  */
6
6
 
7
7
  import React from "react";
8
- import { SettingItem } from "./SettingItem";
8
+ import { SettingsItemCard } from "../../../../presentation/components/SettingsItemCard";
9
9
 
10
10
  export interface StorageClearSettingProps {
11
11
  title?: string;
12
12
  description?: string;
13
13
  onPress?: () => void;
14
14
  iconColor?: string;
15
- titleColor?: string;
16
- isLast?: boolean;
17
15
  }
18
16
 
19
17
  export const StorageClearSetting: React.FC<StorageClearSettingProps> = ({
@@ -21,28 +19,23 @@ export const StorageClearSetting: React.FC<StorageClearSettingProps> = ({
21
19
  description,
22
20
  onPress,
23
21
  iconColor,
24
- titleColor,
25
- isLast = false,
26
22
  }) => {
27
- // Default values for DEV mode
28
- const defaultTitle = title || "Clear All Storage";
29
- const defaultDescription = description || "Clear all local storage data (DEV only)";
30
- const defaultIconColor = iconColor || "#EF4444";
31
- const defaultTitleColor = titleColor || "#EF4444";
32
23
  // Only render in DEV mode
33
24
  if (!__DEV__) {
34
25
  return null;
35
26
  }
36
27
 
28
+ const defaultTitle = title || "Clear All Storage";
29
+ const defaultDescription = description || "Clear all local storage data (DEV only)";
30
+ const defaultIconColor = iconColor || "#EF4444";
31
+
37
32
  return (
38
- <SettingItem
33
+ <SettingsItemCard
39
34
  icon="trash-outline"
40
35
  title={defaultTitle}
41
- value={defaultDescription}
36
+ description={defaultDescription}
42
37
  onPress={onPress}
43
38
  iconColor={defaultIconColor}
44
- titleColor={defaultTitleColor}
45
- isLast={isLast}
46
39
  />
47
40
  );
48
41
  };
@@ -1,9 +1,19 @@
1
1
  /**
2
2
  * Video Tutorials Domain
3
+ *
4
+ * Props-based component - pass tutorials data from app config
5
+ *
6
+ * Usage:
7
+ * import { VideoTutorialsScreen, VideoTutorialCard, VideoTutorial } from '@umituz/react-native-settings';
8
+ *
9
+ * <VideoTutorialsScreen
10
+ * tutorials={myTutorials}
11
+ * featuredTutorials={myFeaturedTutorials}
12
+ * onTutorialPress={(tutorial) => Linking.openURL(tutorial.videoUrl)}
13
+ * />
3
14
  */
4
15
 
5
16
  export * from "./types";
6
17
  export * from "./presentation/screens/VideoTutorialsScreen";
7
18
  export * from "./presentation/components/VideoTutorialCard";
8
- export * from "./presentation/hooks";
9
- export * from "./infrastructure/services/video-tutorial.service";
19
+ export * from "./presentation/components/VideoTutorialSection";
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Video Tutorial Section Component
3
+ * Renders Video Tutorial entry point for settings
4
+ */
5
+
6
+ import React from 'react';
7
+
8
+ export interface VideoTutorialConfig {
9
+ enabled?: boolean;
10
+ title?: string;
11
+ description?: string;
12
+ onPress?: () => void;
13
+ }
14
+
15
+ export interface VideoTutorialSectionProps {
16
+ config: VideoTutorialConfig;
17
+ renderSection: (props: {
18
+ title: string;
19
+ children: React.ReactNode;
20
+ }) => React.ReactElement | null;
21
+ renderItem: (props: {
22
+ title: string;
23
+ icon: any;
24
+ onPress: () => void;
25
+ isLast?: boolean;
26
+ }) => React.ReactElement | null;
27
+ }
28
+
29
+ export const VideoTutorialSection: React.FC<VideoTutorialSectionProps> = ({
30
+ config,
31
+ renderSection,
32
+ renderItem,
33
+ }) => {
34
+ // onPress is required for VideoTutorial section to be functional
35
+ if (!config.enabled || !config.title || !config.description || !config.onPress) {
36
+ return null;
37
+ }
38
+
39
+ return (
40
+ <>
41
+ {renderSection({
42
+ title: config.title,
43
+ children: renderItem({
44
+ title: config.description,
45
+ icon: 'play-circle',
46
+ onPress: config.onPress,
47
+ isLast: true,
48
+ }),
49
+ })}
50
+ </>
51
+ );
52
+ };
@@ -1,6 +1,14 @@
1
1
  /**
2
2
  * Video Tutorials Screen
3
3
  * Single Responsibility: Display video tutorials list
4
+ *
5
+ * Usage:
6
+ * <VideoTutorialsScreen
7
+ * tutorials={[...]}
8
+ * featuredTutorials={[...]}
9
+ * title="Video Tutorials"
10
+ * onTutorialPress={(id) => openVideo(id)}
11
+ * />
4
12
  */
5
13
 
6
14
  import React from "react";
@@ -13,9 +21,16 @@ import {
13
21
  } from "@umituz/react-native-design-system";
14
22
  import type { VideoTutorial } from "../../types";
15
23
  import { VideoTutorialCard } from "../components/VideoTutorialCard";
16
- import { useVideoTutorials, useFeaturedTutorials } from "../hooks";
17
24
 
18
25
  export interface VideoTutorialsScreenProps {
26
+ /**
27
+ * All tutorials to display (required)
28
+ */
29
+ tutorials: VideoTutorial[];
30
+ /**
31
+ * Featured tutorials to display in horizontal scroll (optional)
32
+ */
33
+ featuredTutorials?: VideoTutorial[];
19
34
  /**
20
35
  * Title of the screen
21
36
  */
@@ -29,63 +44,37 @@ export interface VideoTutorialsScreenProps {
29
44
  */
30
45
  allTutorialsTitle?: string;
31
46
  /**
32
- * Error message to show when tutorials fail to load
47
+ * Empty state message when no tutorials
33
48
  */
34
- errorLoadingMessage?: string;
49
+ emptyMessage?: string;
35
50
  /**
36
- * Maximum number of featured tutorials to show
51
+ * Loading state (optional - defaults to false)
37
52
  */
38
- maxFeaturedCount?: number;
53
+ isLoading?: boolean;
39
54
  /**
40
55
  * Callback when a tutorial is pressed
41
56
  */
42
- onTutorialPress?: (tutorialId: string) => void;
43
- /**
44
- * Optional manual override for loading state
45
- */
46
- customIsLoading?: boolean;
47
- /**
48
- * Optional manual override for all tutorials data
49
- */
50
- customAllTutorials?: VideoTutorial[];
51
- /**
52
- * Optional manual override for featured tutorials data
53
- */
54
- customFeaturedTutorials?: VideoTutorial[];
57
+ onTutorialPress?: (tutorial: VideoTutorial) => void;
55
58
  }
56
59
 
57
60
  export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> =
58
61
  React.memo(
59
62
  ({
63
+ tutorials,
64
+ featuredTutorials,
60
65
  title = "Video Tutorials",
61
66
  featuredTitle = "Featured",
62
67
  allTutorialsTitle = "All Tutorials",
63
- errorLoadingMessage = "Failed to load tutorials.",
64
- maxFeaturedCount = 3,
68
+ emptyMessage = "No tutorials available.",
69
+ isLoading = false,
65
70
  onTutorialPress,
66
- customIsLoading,
67
- customAllTutorials,
68
- customFeaturedTutorials,
69
71
  }) => {
70
72
  const tokens = useAppDesignTokens();
71
73
  const styles = getStyles(tokens);
72
74
 
73
- const featuredQuery = useFeaturedTutorials(maxFeaturedCount);
74
- const allQuery = useVideoTutorials();
75
-
76
- const isLoading =
77
- customIsLoading !== undefined
78
- ? customIsLoading
79
- : featuredQuery.isLoading || allQuery.isLoading;
80
- const error = featuredQuery.error || allQuery.error;
81
-
82
- const featuredTutorials =
83
- customFeaturedTutorials || featuredQuery.data || [];
84
- const allTutorials = customAllTutorials || allQuery.data || [];
85
-
86
75
  const handleTutorialPress = React.useCallback(
87
- (tutorialId: string) => {
88
- onTutorialPress?.(tutorialId);
76
+ (tutorial: VideoTutorial) => {
77
+ onTutorialPress?.(tutorial);
89
78
  },
90
79
  [onTutorialPress],
91
80
  );
@@ -94,7 +83,18 @@ export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> =
94
83
  ({ item }: { item: VideoTutorial }) => (
95
84
  <VideoTutorialCard
96
85
  tutorial={item}
97
- onPress={() => handleTutorialPress(item.id)}
86
+ onPress={() => handleTutorialPress(item)}
87
+ />
88
+ ),
89
+ [handleTutorialPress],
90
+ );
91
+
92
+ const renderFeaturedItem = React.useCallback(
93
+ ({ item }: { item: VideoTutorial }) => (
94
+ <VideoTutorialCard
95
+ tutorial={item}
96
+ onPress={() => handleTutorialPress(item)}
97
+ horizontal
98
98
  />
99
99
  ),
100
100
  [handleTutorialPress],
@@ -104,11 +104,14 @@ export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> =
104
104
  return <AtomicSpinner size="lg" fullContainer />;
105
105
  }
106
106
 
107
- if (error) {
107
+ const hasFeatured = featuredTutorials && featuredTutorials.length > 0;
108
+ const hasTutorials = tutorials && tutorials.length > 0;
109
+
110
+ if (!hasTutorials && !hasFeatured) {
108
111
  return (
109
- <View style={styles.errorContainer}>
110
- <AtomicText color="error" type="bodyLarge">
111
- {errorLoadingMessage}
112
+ <View style={styles.emptyContainer}>
113
+ <AtomicText color="secondary" type="bodyLarge">
114
+ {emptyMessage}
112
115
  </AtomicText>
113
116
  </View>
114
117
  );
@@ -120,7 +123,7 @@ export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> =
120
123
  {title}
121
124
  </Text>
122
125
 
123
- {featuredTutorials && featuredTutorials.length > 0 && (
126
+ {hasFeatured && (
124
127
  <View style={styles.section}>
125
128
  <Text
126
129
  style={[
@@ -132,7 +135,7 @@ export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> =
132
135
  </Text>
133
136
  <FlatList
134
137
  data={featuredTutorials}
135
- renderItem={renderTutorialItem}
138
+ renderItem={renderFeaturedItem}
136
139
  keyExtractor={(item: VideoTutorial) => item.id}
137
140
  horizontal
138
141
  showsHorizontalScrollIndicator={false}
@@ -141,23 +144,25 @@ export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> =
141
144
  </View>
142
145
  )}
143
146
 
144
- <View style={styles.section}>
145
- <Text
146
- style={[
147
- styles.sectionTitle,
148
- { color: tokens.colors.textSecondary },
149
- ]}
150
- >
151
- {allTutorialsTitle}
152
- </Text>
153
- <FlatList
154
- data={allTutorials}
155
- renderItem={renderTutorialItem}
156
- keyExtractor={(item: VideoTutorial) => item.id}
157
- showsVerticalScrollIndicator={false}
158
- contentContainerStyle={styles.verticalList}
159
- />
160
- </View>
147
+ {hasTutorials && (
148
+ <View style={styles.section}>
149
+ <Text
150
+ style={[
151
+ styles.sectionTitle,
152
+ { color: tokens.colors.textSecondary },
153
+ ]}
154
+ >
155
+ {allTutorialsTitle}
156
+ </Text>
157
+ <FlatList
158
+ data={tutorials}
159
+ renderItem={renderTutorialItem}
160
+ keyExtractor={(item: VideoTutorial) => item.id}
161
+ showsVerticalScrollIndicator={false}
162
+ contentContainerStyle={styles.verticalList}
163
+ />
164
+ </View>
165
+ )}
161
166
  </ScreenLayout>
162
167
  );
163
168
  },
@@ -165,29 +170,29 @@ export const VideoTutorialsScreen: React.FC<VideoTutorialsScreenProps> =
165
170
 
166
171
  const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
167
172
  StyleSheet.create({
168
- title: {
169
- fontSize: tokens.typography.headlineLarge.fontSize,
170
- fontWeight: "600",
171
- marginBottom: 24,
172
- },
173
- section: {
174
- marginBottom: 24,
175
- },
176
- sectionTitle: {
177
- fontSize: tokens.typography.titleLarge.fontSize,
178
- fontWeight: "500",
179
- marginBottom: 12,
180
- },
181
- horizontalList: {
182
- paddingRight: 16,
183
- },
184
- verticalList: {
185
- paddingBottom: 16,
186
- },
187
- errorContainer: {
188
- flex: 1,
189
- justifyContent: "center",
190
- alignItems: "center",
191
- padding: 20,
192
- },
193
- });
173
+ title: {
174
+ fontSize: tokens.typography.headlineLarge.fontSize,
175
+ fontWeight: "600",
176
+ marginBottom: 24,
177
+ },
178
+ section: {
179
+ marginBottom: 24,
180
+ },
181
+ sectionTitle: {
182
+ fontSize: tokens.typography.titleLarge.fontSize,
183
+ fontWeight: "500",
184
+ marginBottom: 12,
185
+ },
186
+ horizontalList: {
187
+ paddingRight: 16,
188
+ },
189
+ verticalList: {
190
+ paddingBottom: 16,
191
+ },
192
+ emptyContainer: {
193
+ flex: 1,
194
+ justifyContent: "center",
195
+ alignItems: "center",
196
+ padding: 20,
197
+ },
198
+ });
package/src/index.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * User preferences, theme, language, notifications
6
6
  *
7
7
  * Usage:
8
- * import { useSettings, useSettingsStore, SettingsScreen, AppearanceScreen, LanguageSelectionScreen, SettingItem, DisclaimerSetting } from '@umituz/react-native-settings';
8
+ * import { useSettings, SettingsScreen, AppearanceScreen, SettingsItemCard, DisclaimerSetting } from '@umituz/react-native-settings';
9
9
  */
10
10
 
11
11
  // =============================================================================
@@ -74,9 +74,6 @@ export type {
74
74
  // PRESENTATION LAYER - Components
75
75
  // =============================================================================
76
76
 
77
- export { SettingItem } from './presentation/components/SettingItem';
78
- export type { SettingItemProps } from './presentation/components/SettingItem';
79
-
80
77
  export { SettingsItemCard } from './presentation/components/SettingsItemCard';
81
78
  export type { SettingsItemCardProps } from './presentation/components/SettingsItemCard';
82
79
 
@@ -88,14 +85,6 @@ export type { SettingsFooterProps } from './presentation/components/SettingsFoot
88
85
 
89
86
  export { SettingsErrorBoundary } from './presentation/components/SettingsErrorBoundary';
90
87
 
91
- export { StorageClearSetting } from './presentation/components/StorageClearSetting';
92
- export type { StorageClearSettingProps } from './presentation/components/StorageClearSetting';
93
-
94
- export { CloudSyncSetting } from './presentation/components/CloudSyncSetting';
95
- export type { CloudSyncSettingProps } from './presentation/components/CloudSyncSetting';
96
-
97
- export { DevSettingsSection } from './presentation/components/DevSettingsSection';
98
- export type { DevSettingsProps } from './presentation/components/DevSettingsSection';
99
88
 
100
89
  // =============================================================================
101
90
  // DOMAIN EXPORTS - Consolidated Features
@@ -125,6 +114,12 @@ export * from "./domains/rating";
125
114
  // Video Tutorials Domain - Learning resources, tutorials
126
115
  export * from "./domains/video-tutorials";
127
116
 
117
+ // Cloud Sync Domain - Cloud synchronization settings
118
+ export * from "./domains/cloud-sync";
119
+
120
+ // Dev Domain - Development-only settings (DEV mode)
121
+ export * from "./domains/dev";
122
+
128
123
  // =============================================================================
129
124
  // PRESENTATION LAYER - Re-exports from Dependencies
130
125
  // =============================================================================
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { View, Pressable, StyleSheet, ViewStyle } from "react-native";
2
+ import { View, Pressable, StyleSheet, ViewStyle, Switch } from "react-native";
3
3
  import {
4
4
  useAppDesignTokens,
5
5
  AtomicIcon,
@@ -28,6 +28,14 @@ export interface SettingsItemCardProps {
28
28
  iconColor?: string;
29
29
  /** Show chevron even if not clickable */
30
30
  showChevron?: boolean;
31
+ /** Show switch instead of chevron */
32
+ showSwitch?: boolean;
33
+ /** Switch value (when showSwitch is true) */
34
+ switchValue?: boolean;
35
+ /** Switch change handler */
36
+ onSwitchChange?: (value: boolean) => void;
37
+ /** Disable the item */
38
+ disabled?: boolean;
31
39
  }
32
40
 
33
41
  /**
@@ -47,14 +55,40 @@ export const SettingsItemCard: React.FC<SettingsItemCardProps> = ({
47
55
  iconBgColor,
48
56
  iconColor,
49
57
  showChevron,
58
+ showSwitch = false,
59
+ switchValue,
60
+ onSwitchChange,
61
+ disabled = false,
50
62
  }) => {
51
63
  const tokens = useAppDesignTokens();
52
64
  const colors = tokens.colors;
53
65
 
54
66
  const defaultIconBg = iconBgColor || `${colors.primary}15`;
55
67
  const defaultIconColor = iconColor || colors.primary;
56
- const isClickable = !!onPress;
57
- const shouldShowChevron = showChevron ?? isClickable;
68
+ const isClickable = !!onPress && !showSwitch;
69
+ const shouldShowChevron = !showSwitch && (showChevron ?? isClickable);
70
+
71
+ const renderRightElement = () => {
72
+ if (showSwitch) {
73
+ return (
74
+ <Switch
75
+ value={switchValue}
76
+ onValueChange={onSwitchChange}
77
+ trackColor={{
78
+ false: colors.surfaceVariant,
79
+ true: colors.primary,
80
+ }}
81
+ thumbColor={colors.surface}
82
+ ios_backgroundColor={colors.surfaceVariant}
83
+ disabled={disabled}
84
+ />
85
+ );
86
+ }
87
+ if (shouldShowChevron) {
88
+ return <AtomicIcon name={rightIcon} size="sm" color="secondary" />;
89
+ }
90
+ return null;
91
+ };
58
92
 
59
93
  const renderContent = () => (
60
94
  <View style={styles.content}>
@@ -64,9 +98,9 @@ export const SettingsItemCard: React.FC<SettingsItemCardProps> = ({
64
98
  <View style={styles.textContainer}>
65
99
  <AtomicText
66
100
  type="bodyLarge"
67
- color="onSurface"
101
+ color={disabled ? "surfaceVariant" : "onSurface"}
68
102
  numberOfLines={1}
69
- style={{ marginBottom: tokens.spacing.xs }}
103
+ style={{ marginBottom: description ? tokens.spacing.xs : 0, opacity: disabled ? 0.6 : 1 }}
70
104
  >
71
105
  {title}
72
106
  </AtomicText>
@@ -76,9 +110,7 @@ export const SettingsItemCard: React.FC<SettingsItemCardProps> = ({
76
110
  </AtomicText>
77
111
  )}
78
112
  </View>
79
- {shouldShowChevron && (
80
- <AtomicIcon name={rightIcon} size="sm" color="secondary" />
81
- )}
113
+ {renderRightElement()}
82
114
  </View>
83
115
  );
84
116
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { SettingsConfig, CustomSettingsSection } from "../screens/types";
6
- import type { DevSettingsProps } from "../components/DevSettingsSection";
6
+ import type { DevSettingsProps } from "../../domains/dev";
7
7
  import type { FAQCategory } from "../../domains/faqs";
8
8
 
9
9
  /**
@@ -16,7 +16,7 @@ import { SettingsErrorBoundary } from "../components/SettingsErrorBoundary";
16
16
  import { normalizeSettingsConfig } from "./utils/normalizeConfig";
17
17
  import { useFeatureDetection } from "./hooks/useFeatureDetection";
18
18
  import type { SettingsConfig, CustomSettingsSection } from "./types";
19
- import type { DevSettingsProps } from "../components/DevSettingsSection";
19
+ import type { DevSettingsProps } from "../../domains/dev";
20
20
 
21
21
  export interface SettingsScreenProps {
22
22
  config?: SettingsConfig;
@@ -3,7 +3,7 @@ import { View, StyleSheet } from "react-native";
3
3
  import { useLocalization } from "@umituz/react-native-localization";
4
4
  import { SettingsFooter } from "../../components/SettingsFooter";
5
5
  import { SettingsSection } from "../../components/SettingsSection";
6
- import { DevSettingsSection, DevSettingsProps } from "../../components/DevSettingsSection";
6
+ import { DevSettingsSection, DevSettingsProps } from "../../../domains/dev";
7
7
  import { DisclaimerSetting } from "../../../domains/disclaimer";
8
8
  import { ProfileSectionLoader } from "./sections/ProfileSectionLoader";
9
9
  import { FeatureSettingsSection } from "./sections/FeatureSettingsSection";
@@ -3,7 +3,7 @@ import { useLocalization } from "@umituz/react-native-localization";
3
3
  import { SupportSection } from "../../../../domains/feedback/presentation/components/SupportSection";
4
4
  import { FAQSection } from "../../../../domains/faqs/presentation/components/FAQSection";
5
5
  import { SettingsSection } from "../../../components/SettingsSection";
6
- import { SettingItem } from "../../../components/SettingItem";
6
+ import { SettingsItemCard } from "../../../components/SettingsItemCard";
7
7
 
8
8
  interface SupportSettingsSectionProps {
9
9
  features: any;
@@ -23,7 +23,7 @@ export const SupportSettingsSection: React.FC<SupportSettingsSectionProps> = ({
23
23
  {(features.feedback || features.rating) && (
24
24
  <SupportSection
25
25
  renderSection={(props: any) => <>{props.children}</>}
26
- renderItem={(props: any) => <SettingItem {...props} />}
26
+ renderItem={(props: any) => <SettingsItemCard title={props.title} icon={props.icon} onPress={props.onPress} />}
27
27
  feedbackConfig={{
28
28
  enabled: features.feedback,
29
29
  config: {
@@ -70,7 +70,7 @@ export const SupportSettingsSection: React.FC<SupportSettingsSectionProps> = ({
70
70
  {features.faqs && (
71
71
  <FAQSection
72
72
  renderSection={(props: any) => <>{props.children}</>}
73
- renderItem={(props: any) => <SettingItem {...props} />}
73
+ renderItem={(props: any) => <SettingsItemCard title={props.title} icon={props.icon} onPress={props.onPress} />}
74
74
  config={{
75
75
  enabled: features.faqs,
76
76
  ...normalizedConfig.faqs.config,
@@ -1,117 +0,0 @@
1
- /**
2
- * Video Tutorial Service
3
- * Single Responsibility: Handle video tutorial data operations
4
- */
5
-
6
- import {
7
- collection,
8
- getDocs,
9
- query,
10
- orderBy,
11
- where,
12
- limit,
13
- } from "firebase/firestore";
14
- import type { Firestore, QueryConstraint } from "firebase/firestore";
15
- import type {
16
- VideoTutorial,
17
- VideoTutorialCategory,
18
- VideoTutorialFilters,
19
- } from "../../types";
20
-
21
- export interface VideoTutorialServiceConfig {
22
- db?: Firestore;
23
- collectionName?: string;
24
- }
25
-
26
- class VideoTutorialService {
27
- private config: VideoTutorialServiceConfig = {
28
- collectionName: "video_tutorials",
29
- };
30
-
31
- initialize(config: VideoTutorialServiceConfig) {
32
- this.config = { ...this.config, ...config };
33
- }
34
-
35
- private get db(): Firestore {
36
- if (!this.config.db) {
37
- throw new Error("VideoTutorialService: Firestore not initialized. Call initialize({ db }) first.");
38
- }
39
- return this.config.db;
40
- }
41
-
42
- private get collectionName(): string {
43
- return this.config.collectionName || "video_tutorials";
44
- }
45
-
46
- async getAllTutorials(
47
- filters?: VideoTutorialFilters,
48
- ): Promise<VideoTutorial[]> {
49
- const constraints: QueryConstraint[] = [];
50
-
51
- if (filters?.category) {
52
- constraints.push(where("category", "==", filters.category));
53
- }
54
-
55
- if (filters?.difficulty) {
56
- constraints.push(where("difficulty", "==", filters.difficulty));
57
- }
58
-
59
- if (filters?.featured !== undefined) {
60
- constraints.push(where("featured", "==", filters.featured));
61
- }
62
-
63
- constraints.push(orderBy("createdAt", "desc"));
64
-
65
- const q = query(collection(this.db, this.collectionName), ...constraints);
66
- const snapshot = await getDocs(q);
67
-
68
- return snapshot.docs.map((doc) => this.mapDocumentToTutorial(doc));
69
- }
70
-
71
- async getFeaturedTutorials(maxCount: number = 5): Promise<VideoTutorial[]> {
72
- const q = query(
73
- collection(this.db, this.collectionName),
74
- where("featured", "==", true),
75
- orderBy("createdAt", "desc"),
76
- limit(maxCount),
77
- );
78
-
79
- const snapshot = await getDocs(q);
80
- return snapshot.docs.map((doc) => this.mapDocumentToTutorial(doc));
81
- }
82
-
83
- async getTutorialsByCategory(
84
- category: VideoTutorialCategory,
85
- ): Promise<VideoTutorial[]> {
86
- const q = query(
87
- collection(this.db, this.collectionName),
88
- where("category", "==", category),
89
- orderBy("createdAt", "desc"),
90
- );
91
-
92
- const snapshot = await getDocs(q);
93
- return snapshot.docs.map((doc) => this.mapDocumentToTutorial(doc));
94
- }
95
-
96
- private mapDocumentToTutorial(doc: any): VideoTutorial {
97
- const data = doc.data();
98
- return {
99
- id: doc.id,
100
- title: data.title || "",
101
- description: data.description || "",
102
- videoUrl: data.videoUrl || "",
103
- thumbnailUrl: data.thumbnailUrl || "",
104
- duration: data.duration || 0,
105
- category: data.category || "getting-started",
106
- difficulty: data.difficulty || "beginner",
107
- featured: data.featured || false,
108
- tags: data.tags || [],
109
- createdAt: data.createdAt?.toDate() || new Date(),
110
- updatedAt: data.updatedAt?.toDate() || new Date(),
111
- viewCount: data.viewCount || 0,
112
- };
113
- }
114
- }
115
-
116
- export const videoTutorialService = new VideoTutorialService();
117
-
@@ -1,36 +0,0 @@
1
- /**
2
- * Video Tutorial Hooks
3
- * Single Responsibility: Provide React hooks for video tutorials
4
- */
5
-
6
- import { useQuery } from "@tanstack/react-query";
7
- import { videoTutorialService } from "../../infrastructure/services/video-tutorial.service";
8
- import type {
9
- VideoTutorial,
10
- VideoTutorialCategory,
11
- VideoTutorialFilters,
12
- } from "../../types";
13
-
14
- export function useVideoTutorials(filters?: VideoTutorialFilters) {
15
- return useQuery({
16
- queryKey: ["video-tutorials", filters],
17
- queryFn: () => videoTutorialService.getAllTutorials(filters),
18
- staleTime: 5 * 60 * 1000, // 5 minutes
19
- });
20
- }
21
-
22
- export function useFeaturedTutorials(maxCount: number = 5) {
23
- return useQuery({
24
- queryKey: ["featured-tutorials", maxCount],
25
- queryFn: () => videoTutorialService.getFeaturedTutorials(maxCount),
26
- staleTime: 10 * 60 * 1000, // 10 minutes
27
- });
28
- }
29
-
30
- export function useTutorialsByCategory(category: VideoTutorialCategory) {
31
- return useQuery({
32
- queryKey: ["tutorials-by-category", category],
33
- queryFn: () => videoTutorialService.getTutorialsByCategory(category),
34
- staleTime: 5 * 60 * 1000, // 5 minutes
35
- });
36
- }
@@ -1,145 +0,0 @@
1
- import React from "react";
2
- import { View, StyleSheet, Switch } from "react-native";
3
- import {
4
- AtomicIcon,
5
- AtomicText,
6
- useAppDesignTokens,
7
- } from "@umituz/react-native-design-system";
8
- import { SettingsItemCard } from "./SettingsItemCard";
9
-
10
- export interface SettingItemProps {
11
- icon: string;
12
- title: string;
13
- value?: string;
14
- onPress?: () => void;
15
- showSwitch?: boolean;
16
- switchValue?: boolean;
17
- onSwitchChange?: (value: boolean) => void;
18
- isLast?: boolean;
19
- iconColor?: string;
20
- titleColor?: string;
21
- testID?: string;
22
- disabled?: boolean;
23
- }
24
-
25
- /**
26
- * SettingItem - Enhanced ListItem for Settings
27
- * Uses design system's ListItem molecule with custom switch support
28
- */
29
- export const SettingItem: React.FC<SettingItemProps> = ({
30
- icon,
31
- title,
32
- value,
33
- onPress,
34
- showSwitch = false,
35
- switchValue,
36
- onSwitchChange,
37
- isLast = false,
38
- iconColor,
39
- titleColor,
40
- testID,
41
- disabled = false,
42
- }) => {
43
- const tokens = useAppDesignTokens();
44
-
45
- // For switch items, we need custom rendering
46
- if (showSwitch) {
47
- return (
48
- <View
49
- style={[
50
- styles.switchContainer,
51
- {
52
- borderBottomWidth: isLast ? 0 : 1,
53
- borderBottomColor: `${tokens.colors.onSurface}10`,
54
- }
55
- ]}
56
- >
57
- <View style={styles.switchContent}>
58
- <View style={[
59
- styles.iconWrapper,
60
- { backgroundColor: iconColor ? `${iconColor}15` : `${tokens.colors.primary}15` }
61
- ]}>
62
- <AtomicIcon
63
- name={icon}
64
- size="md"
65
- customColor={iconColor || tokens.colors.primary}
66
- />
67
- </View>
68
- <View style={styles.textContainer}>
69
- <AtomicText
70
- type="bodyLarge"
71
- color={disabled ? "surfaceVariant" : "onSurface"}
72
- style={[
73
- titleColor ? { color: titleColor } : {},
74
- { opacity: disabled ? 0.6 : 1 }
75
- ]}
76
- numberOfLines={1}
77
- >
78
- {title}
79
- </AtomicText>
80
- {value && (
81
- <AtomicText
82
- type="bodySmall"
83
- color="secondary"
84
- numberOfLines={2}
85
- style={{ marginTop: 2 }}
86
- >
87
- {value}
88
- </AtomicText>
89
- )}
90
- </View>
91
- </View>
92
- <Switch
93
- value={switchValue}
94
- onValueChange={onSwitchChange}
95
- trackColor={{
96
- false: tokens.colors.surfaceVariant,
97
- true: tokens.colors.primary
98
- }}
99
- thumbColor={tokens.colors.surface}
100
- ios_backgroundColor={tokens.colors.surfaceVariant}
101
- disabled={disabled}
102
- />
103
- </View>
104
- );
105
- }
106
-
107
- // Use SettingsItemCard for regular items
108
- return (
109
- <SettingsItemCard
110
- title={title}
111
- description={value}
112
- icon={icon as any}
113
- onPress={onPress}
114
- iconColor={iconColor}
115
- />
116
- );
117
- };
118
-
119
- const styles = StyleSheet.create({
120
- switchContainer: {
121
- flexDirection: "row",
122
- alignItems: "center",
123
- justifyContent: "space-between",
124
- paddingHorizontal: 16,
125
- paddingVertical: 12,
126
- minHeight: 64,
127
- },
128
- switchContent: {
129
- flexDirection: "row",
130
- alignItems: "center",
131
- flex: 1,
132
- },
133
- iconWrapper: {
134
- width: 40,
135
- height: 40,
136
- borderRadius: 10,
137
- justifyContent: "center",
138
- alignItems: "center",
139
- marginRight: 12,
140
- },
141
- textContainer: {
142
- flex: 1,
143
- paddingRight: 8,
144
- },
145
- });
@@ -1,189 +0,0 @@
1
- /**
2
- * Tests for SettingItem Component
3
- */
4
-
5
- import React from 'react';
6
- import { render, fireEvent } from '@testing-library/react-native';
7
- import { SettingItem } from '../SettingItem';
8
-
9
- // Mock lucide-react-native
10
- jest.mock('lucide-react-native', () => ({
11
- Bell: 'Bell',
12
- Palette: 'Palette',
13
- ChevronRight: 'ChevronRight',
14
- }));
15
-
16
- // Mock dependencies
17
- jest.mock('@umituz/react-native-design-system', () => ({
18
- useAppDesignTokens: () => ({
19
- colors: {
20
- backgroundPrimary: '#ffffff',
21
- primary: '#007AFF',
22
- textPrimary: '#000000',
23
- textSecondary: '#666666',
24
- },
25
- spacing: {
26
- md: 16,
27
- },
28
- }),
29
- }));
30
-
31
- describe('SettingItem', () => {
32
- it('renders correctly with basic props', () => {
33
- const { getByText, getByTestId } = render(
34
- <SettingItem
35
- icon={Bell}
36
- title="Test Setting"
37
- testID="test-setting"
38
- />
39
- );
40
-
41
- expect(getByText('Test Setting')).toBeTruthy();
42
- expect(getByTestId('test-setting')).toBeTruthy();
43
- });
44
-
45
- it('displays value when provided', () => {
46
- const { getByText } = render(
47
- <SettingItem
48
- icon={Bell}
49
- title="Test Setting"
50
- value="Test Value"
51
- />
52
- );
53
-
54
- expect(getByText('Test Setting')).toBeTruthy();
55
- expect(getByText('Test Value')).toBeTruthy();
56
- });
57
-
58
- it('shows switch when showSwitch is true', () => {
59
- const { getByTestId, queryByTestId } = render(
60
- <SettingItem
61
- icon={Bell}
62
- title="Test Setting"
63
- showSwitch={true}
64
- switchValue={true}
65
- testID="test-setting"
66
- />
67
- );
68
-
69
- expect(getByTestId('test-setting')).toBeTruthy();
70
- // Switch should be present, chevron should not
71
- });
72
-
73
- it('calls onPress when pressed', () => {
74
- const mockOnPress = jest.fn();
75
- const { getByTestId } = render(
76
- <SettingItem
77
- icon={Bell}
78
- title="Test Setting"
79
- onPress={mockOnPress}
80
- testID="test-setting"
81
- />
82
- );
83
-
84
- fireEvent.press(getByTestId('test-setting'));
85
- expect(mockOnPress).toHaveBeenCalledTimes(1);
86
- });
87
-
88
- it('calls onSwitchChange when switch is toggled', () => {
89
- const mockOnSwitchChange = jest.fn();
90
- const { getByRole } = render(
91
- <SettingItem
92
- icon={Bell}
93
- title="Test Setting"
94
- showSwitch={true}
95
- switchValue={false}
96
- onSwitchChange={mockOnSwitchChange}
97
- />
98
- );
99
-
100
- const switchElement = getByRole('switch');
101
- fireEvent(switchElement, 'valueChange', true);
102
- expect(mockOnSwitchChange).toHaveBeenCalledWith(true);
103
- });
104
-
105
- it('applies custom colors', () => {
106
- const { getByText } = render(
107
- <SettingItem
108
- icon={Bell}
109
- title="Test Setting"
110
- iconColor="#FF0000"
111
- titleColor="#00FF00"
112
- />
113
- );
114
-
115
- expect(getByText('Test Setting')).toBeTruthy();
116
- });
117
-
118
- it('shows disabled state correctly', () => {
119
- const mockOnPress = jest.fn();
120
- const { getByTestId } = render(
121
- <SettingItem
122
- icon={Bell}
123
- title="Test Setting"
124
- disabled={true}
125
- onPress={mockOnPress}
126
- testID="test-setting"
127
- />
128
- );
129
-
130
- const pressable = getByTestId('test-setting');
131
- expect(pressable.props.disabled).toBe(true);
132
- });
133
-
134
- it('does not show divider when isLast is true', () => {
135
- const { queryByTestId } = render(
136
- <SettingItem
137
- icon={Bell}
138
- title="Test Setting"
139
- isLast={true}
140
- />
141
- );
142
-
143
- // Divider should not be present
144
- expect(queryByTestId('setting-divider')).toBeNull();
145
- });
146
-
147
- it('renders with different icon types', () => {
148
- const { getByText } = render(
149
- <SettingItem
150
- icon={Palette}
151
- title="Appearance Setting"
152
- />
153
- );
154
-
155
- expect(getByText('Appearance Setting')).toBeTruthy();
156
- });
157
-
158
- it('handles long text correctly', () => {
159
- const longTitle = 'This is a very long title that should be truncated properly';
160
- const longValue = 'This is a very long value that should be truncated properly and should not break the layout';
161
-
162
- const { getByText } = render(
163
- <SettingItem
164
- icon={Bell}
165
- title={longTitle}
166
- value={longValue}
167
- />
168
- );
169
-
170
- expect(getByText(longTitle)).toBeTruthy();
171
- expect(getByText(longValue)).toBeTruthy();
172
- });
173
-
174
- it('applies custom switch colors', () => {
175
- const { getByRole } = render(
176
- <SettingItem
177
- icon={Bell}
178
- title="Test Setting"
179
- showSwitch={true}
180
- switchValue={true}
181
- switchThumbColor="#FF0000"
182
- switchTrackColors={{ false: '#CCCCCC', true: '#00FF00' }}
183
- />
184
- );
185
-
186
- const switchElement = getByRole('switch');
187
- expect(switchElement).toBeTruthy();
188
- });
189
- });