@umituz/react-native-settings 4.17.14 → 4.17.16

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 (103) hide show
  1. package/package.json +16 -15
  2. package/src/domains/about/__tests__/integration.test.tsx +328 -0
  3. package/src/domains/about/__tests__/types.d.ts +5 -0
  4. package/src/domains/about/domain/entities/AppInfo.ts +74 -0
  5. package/src/domains/about/domain/entities/__tests__/AppInfo.test.ts +93 -0
  6. package/src/domains/about/domain/repositories/IAboutRepository.ts +22 -0
  7. package/src/domains/about/index.ts +10 -0
  8. package/src/domains/about/infrastructure/repositories/AboutRepository.ts +68 -0
  9. package/src/domains/about/infrastructure/repositories/__tests__/AboutRepository.test.ts +153 -0
  10. package/src/domains/about/presentation/components/AboutContent.tsx +104 -0
  11. package/src/domains/about/presentation/components/AboutHeader.tsx +79 -0
  12. package/src/domains/about/presentation/components/AboutSection.tsx +134 -0
  13. package/src/domains/about/presentation/components/AboutSettingItem.tsx +208 -0
  14. package/src/domains/about/presentation/components/__tests__/AboutContent.simple.test.tsx +178 -0
  15. package/src/domains/about/presentation/components/__tests__/AboutContent.test.tsx +293 -0
  16. package/src/domains/about/presentation/components/__tests__/AboutHeader.test.tsx +201 -0
  17. package/src/domains/about/presentation/components/__tests__/AboutSettingItem.test.tsx +71 -0
  18. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.simple.test.tsx +229 -0
  19. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.test.tsx +240 -0
  20. package/src/domains/about/presentation/hooks/useAboutInfo.ts +262 -0
  21. package/src/domains/about/presentation/screens/AboutScreen.tsx +195 -0
  22. package/src/domains/about/presentation/screens/__tests__/AboutScreen.simple.test.tsx +199 -0
  23. package/src/domains/about/presentation/screens/__tests__/AboutScreen.test.tsx +366 -0
  24. package/src/domains/about/types/global.d.ts +15 -0
  25. package/src/domains/about/utils/__tests__/index.test.ts +408 -0
  26. package/src/domains/about/utils/index.ts +160 -0
  27. package/src/domains/appearance/__tests__/components/AppearanceScreen.test.tsx +195 -0
  28. package/src/domains/appearance/__tests__/hooks/index.test.tsx +232 -0
  29. package/src/domains/appearance/__tests__/integration/index.test.tsx +207 -0
  30. package/src/domains/appearance/__tests__/services/appearanceService.test.ts +299 -0
  31. package/src/domains/appearance/__tests__/setup.ts +96 -0
  32. package/src/domains/appearance/__tests__/stores/appearanceStore.test.tsx +175 -0
  33. package/src/domains/appearance/data/colorPalettes.ts +94 -0
  34. package/src/domains/appearance/hooks/index.ts +6 -0
  35. package/src/domains/appearance/hooks/useAppearance.ts +61 -0
  36. package/src/domains/appearance/hooks/useAppearanceActions.ts +144 -0
  37. package/src/domains/appearance/index.ts +7 -0
  38. package/src/domains/appearance/infrastructure/services/appearanceService.ts +301 -0
  39. package/src/domains/appearance/infrastructure/services/systemThemeDetection.ts +79 -0
  40. package/src/domains/appearance/infrastructure/services/validation.ts +91 -0
  41. package/src/domains/appearance/infrastructure/storage/appearanceStorage.ts +120 -0
  42. package/src/domains/appearance/infrastructure/stores/appearanceStore.ts +132 -0
  43. package/src/domains/appearance/presentation/components/AppearanceHeader.tsx +67 -0
  44. package/src/domains/appearance/presentation/components/AppearancePreview.tsx +141 -0
  45. package/src/domains/appearance/presentation/components/AppearanceSection.tsx +139 -0
  46. package/src/domains/appearance/presentation/components/ColorPicker.tsx +113 -0
  47. package/src/domains/appearance/presentation/components/CustomColorsSection.tsx +186 -0
  48. package/src/domains/appearance/presentation/components/ThemeModeSection.tsx +110 -0
  49. package/src/domains/appearance/presentation/components/ThemeOption.tsx +138 -0
  50. package/src/domains/appearance/presentation/components/index.ts +6 -0
  51. package/src/domains/appearance/presentation/screens/AppearanceScreen.tsx +226 -0
  52. package/src/domains/appearance/presentation/screens/index.ts +2 -0
  53. package/src/domains/appearance/types/index.ts +54 -0
  54. package/src/domains/faqs/domain/entities/FAQEntity.ts +16 -0
  55. package/src/domains/faqs/domain/services/FAQSearchService.ts +36 -0
  56. package/src/domains/faqs/domain/services/index.ts +1 -0
  57. package/src/domains/faqs/index.ts +7 -0
  58. package/src/domains/faqs/presentation/components/FAQCategory.tsx +71 -0
  59. package/src/domains/faqs/presentation/components/FAQEmptyState.tsx +75 -0
  60. package/src/domains/faqs/presentation/components/FAQItem.tsx +103 -0
  61. package/src/domains/faqs/presentation/components/FAQSearchBar.tsx +70 -0
  62. package/src/domains/faqs/presentation/components/FAQSection.tsx +50 -0
  63. package/src/domains/faqs/presentation/components/index.ts +18 -0
  64. package/src/domains/faqs/presentation/hooks/index.ts +6 -0
  65. package/src/domains/faqs/presentation/hooks/useFAQExpansion.ts +51 -0
  66. package/src/domains/faqs/presentation/hooks/useFAQSearch.ts +33 -0
  67. package/src/domains/faqs/presentation/screens/FAQScreen.tsx +129 -0
  68. package/src/domains/faqs/presentation/screens/index.ts +2 -0
  69. package/src/domains/feedback/domain/entities/FeedbackEntity.ts +92 -0
  70. package/src/domains/feedback/domain/repositories/IFeedbackRepository.ts +28 -0
  71. package/src/domains/feedback/index.ts +6 -0
  72. package/src/domains/feedback/presentation/components/FeedbackForm.tsx +189 -0
  73. package/src/domains/feedback/presentation/components/FeedbackModal.tsx +111 -0
  74. package/src/domains/feedback/presentation/components/SupportSection.tsx +160 -0
  75. package/src/domains/feedback/presentation/hooks/useDeleteFeedback.ts +25 -0
  76. package/src/domains/feedback/presentation/hooks/useFeedbackForm.ts +59 -0
  77. package/src/domains/feedback/presentation/hooks/useSubmitFeedback.ts +55 -0
  78. package/src/domains/feedback/presentation/hooks/useUserFeedback.ts +29 -0
  79. package/src/domains/legal/__tests__/ContentValidationService.test.ts +195 -0
  80. package/src/domains/legal/__tests__/StyleCacheService.test.ts +110 -0
  81. package/src/domains/legal/__tests__/UrlHandlerService.test.ts +71 -0
  82. package/src/domains/legal/__tests__/setup.ts +82 -0
  83. package/src/domains/legal/domain/entities/LegalConfig.ts +26 -0
  84. package/src/domains/legal/domain/services/ContentValidationService.ts +89 -0
  85. package/src/domains/legal/domain/services/StyleCacheService.ts +97 -0
  86. package/src/domains/legal/domain/services/UrlHandlerService.ts +128 -0
  87. package/src/domains/legal/index.ts +8 -0
  88. package/src/domains/legal/presentation/components/LegalItem.tsx +177 -0
  89. package/src/domains/legal/presentation/components/LegalLinks.tsx +154 -0
  90. package/src/domains/legal/presentation/components/LegalSection.tsx +134 -0
  91. package/src/domains/legal/presentation/screens/LegalScreen.tsx +237 -0
  92. package/src/domains/legal/presentation/screens/PrivacyPolicyScreen.tsx +214 -0
  93. package/src/domains/legal/presentation/screens/TermsOfServiceScreen.tsx +214 -0
  94. package/src/index.ts +19 -0
  95. package/src/presentation/components/DevSettingsSection.tsx +2 -2
  96. package/src/presentation/components/SettingItem.tsx +2 -2
  97. package/src/presentation/components/SettingsErrorBoundary.tsx +2 -2
  98. package/src/presentation/components/SettingsFooter.tsx +2 -2
  99. package/src/presentation/components/SettingsSection.tsx +2 -2
  100. package/src/presentation/navigation/SettingsStackNavigator.tsx +2 -2
  101. package/src/presentation/screens/SettingsScreen.tsx +2 -2
  102. package/src/presentation/screens/components/SettingsContent.tsx +2 -2
  103. package/src/presentation/screens/components/SettingsHeader.tsx +2 -2
@@ -0,0 +1,61 @@
1
+ /**
2
+ * useAppearance Hook
3
+ *
4
+ * Hook for accessing appearance state and actions
5
+ * Single Responsibility: Presentation layer data access
6
+ */
7
+
8
+ import { useCallback } from "react";
9
+ import { useAppearanceStore } from "../infrastructure/stores/appearanceStore";
10
+ import { appearanceService } from "../infrastructure/services/appearanceService";
11
+ import type { ThemeMode, CustomThemeColors } from "../types";
12
+
13
+ export const useAppearance = () => {
14
+ const store = useAppearanceStore();
15
+
16
+ const setThemeMode = useCallback(
17
+ async (mode: ThemeMode) => {
18
+ await appearanceService.setThemeMode(mode);
19
+ },
20
+ []
21
+ );
22
+
23
+ const toggleTheme = useCallback(
24
+ async () => {
25
+ await appearanceService.toggleTheme();
26
+ },
27
+ []
28
+ );
29
+
30
+ const setCustomColors = useCallback(
31
+ async (colors: CustomThemeColors) => {
32
+ await appearanceService.setCustomColors(colors);
33
+ },
34
+ []
35
+ );
36
+
37
+ const resetCustomColors = useCallback(
38
+ async () => {
39
+ await appearanceService.resetCustomColors();
40
+ },
41
+ []
42
+ );
43
+
44
+ const reset = useCallback(
45
+ async () => {
46
+ await appearanceService.reset();
47
+ },
48
+ []
49
+ );
50
+
51
+ return {
52
+ themeMode: store.settings.themeMode,
53
+ customColors: store.settings.customColors,
54
+ isInitialized: store.isInitialized,
55
+ setThemeMode,
56
+ toggleTheme,
57
+ setCustomColors,
58
+ resetCustomColors,
59
+ reset,
60
+ };
61
+ };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Appearance Actions Hook
3
+ * Single Responsibility: Handle appearance-related presentation actions
4
+ * Business logic extracted to service layer
5
+ */
6
+
7
+ import { useCallback, useState, useEffect, useRef } from "react";
8
+ import { useAppearance } from "./useAppearance";
9
+ import type {
10
+ ThemeMode,
11
+ CustomThemeColors,
12
+ } from "@umituz/react-native-design-system";
13
+
14
+ export interface UseAppearanceActionsReturn {
15
+ localCustomColors: CustomThemeColors;
16
+ handleThemeSelect: (mode: ThemeMode) => Promise<void>;
17
+ handleColorChange: (key: keyof CustomThemeColors, color: string) => void;
18
+ handleResetColors: (onConfirm?: () => void) => void;
19
+ }
20
+
21
+ export interface AppearanceActionsConfig {
22
+ onResetConfirm?: () => void;
23
+ onResetCancel?: () => void;
24
+ resetTitle?: string;
25
+ resetMessage?: string;
26
+ confirmLabel?: string;
27
+ cancelLabel?: string;
28
+ }
29
+
30
+ export function useAppearanceActions(
31
+ config?: AppearanceActionsConfig
32
+ ): UseAppearanceActionsReturn {
33
+ const { customColors, setThemeMode, setCustomColors, resetCustomColors } =
34
+ useAppearance();
35
+
36
+ // Use ref to prevent unnecessary re-renders and memory leaks
37
+ const configRef = useRef(config);
38
+ configRef.current = config;
39
+
40
+ // Initialize local state with custom colors, prevent unnecessary updates
41
+ const [localCustomColors, setLocalCustomColors] = useState<CustomThemeColors>(() =>
42
+ customColors || {}
43
+ );
44
+
45
+ // Sync local state with store changes, but only if different
46
+ useEffect(() => {
47
+ if (customColors && JSON.stringify(customColors) !== JSON.stringify(localCustomColors)) {
48
+ if (__DEV__) {
49
+ console.log("[useAppearanceActions] Syncing local colors with store");
50
+ }
51
+ setLocalCustomColors(customColors);
52
+ }
53
+ }, [customColors]); // Only depend on customColors, not localCustomColors
54
+
55
+ const handleThemeSelect = useCallback(
56
+ async (mode: ThemeMode) => {
57
+ try {
58
+ await setThemeMode(mode);
59
+ } catch (error) {
60
+ if (__DEV__) {
61
+ console.error("[useAppearanceActions] Failed to set theme mode:", error);
62
+ }
63
+ }
64
+ },
65
+ [setThemeMode],
66
+ );
67
+
68
+ const handleColorChange = useCallback(
69
+ (key: keyof CustomThemeColors, color: string) => {
70
+ try {
71
+ // Prevent unnecessary updates if color hasn't changed
72
+ if (localCustomColors[key] === color) {
73
+ return;
74
+ }
75
+
76
+ const newColors = {
77
+ ...localCustomColors,
78
+ [key]: color,
79
+ };
80
+
81
+ if (__DEV__) {
82
+ console.log("[useAppearanceActions] Updating color:", key, color);
83
+ }
84
+
85
+ setLocalCustomColors(newColors);
86
+ setCustomColors(newColors);
87
+ } catch (error) {
88
+ if (__DEV__) {
89
+ console.error("[useAppearanceActions] Failed to update color:", error);
90
+ }
91
+ }
92
+ },
93
+ [localCustomColors, setCustomColors],
94
+ );
95
+
96
+ const handleResetColors = useCallback(
97
+ (onConfirm?: () => void) => {
98
+ try {
99
+ // Generic reset handler - the host app should handle the UI confirmation
100
+ const resetAction = async () => {
101
+ try {
102
+ setLocalCustomColors({});
103
+ await resetCustomColors();
104
+ onConfirm?.();
105
+ configRef.current?.onResetConfirm?.();
106
+ } catch (error) {
107
+ if (__DEV__) {
108
+ console.error("[useAppearanceActions] Failed to reset colors:", error);
109
+ }
110
+ }
111
+ };
112
+
113
+ // If no custom config provided, just reset directly
114
+ if (!configRef.current?.resetTitle) {
115
+ resetAction();
116
+ }
117
+ // Otherwise, the host app should handle showing the confirmation dialog
118
+ // and call resetAction when confirmed
119
+ } catch (error) {
120
+ if (__DEV__) {
121
+ console.error("[useAppearanceActions] Failed to handle reset colors:", error);
122
+ }
123
+ }
124
+ },
125
+ [resetCustomColors],
126
+ );
127
+
128
+ // Cleanup effect
129
+ useEffect(() => {
130
+ return () => {
131
+ // Cleanup any pending operations or references
132
+ if (__DEV__) {
133
+ console.log("[useAppearanceActions] Cleanup");
134
+ }
135
+ };
136
+ }, []);
137
+
138
+ return {
139
+ localCustomColors,
140
+ handleThemeSelect,
141
+ handleColorChange,
142
+ handleResetColors,
143
+ };
144
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Appearance Domain
3
+ * Theme management, dark mode
4
+ */
5
+
6
+ export * from './presentation/screens/AppearanceScreen';
7
+ export * from './presentation/components/ThemeSwitcher';
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Appearance Service
3
+ *
4
+ * Business logic for appearance management
5
+ * Single Responsibility: Business logic only, no presentation logic
6
+ */
7
+
8
+ import { AppearanceStorage } from "../storage/appearanceStorage";
9
+ import { useAppearanceStore } from "../stores/appearanceStore";
10
+ import {
11
+ useTheme,
12
+ useDesignSystemTheme,
13
+ type ThemeMode,
14
+ type CustomThemeColors,
15
+ } from "@umituz/react-native-design-system";
16
+ import { getSystemTheme } from "./systemThemeDetection";
17
+ import { validateAppearanceSettings } from "./validation";
18
+ import type { AppearanceSettings } from "../../types";
19
+
20
+ export class AppearanceService {
21
+ private readonly DEFAULT_THEME_MODE: ThemeMode = "light"; // Use system preference as default
22
+ private _isInitialized = false;
23
+ private initPromise: Promise<void> | null = null;
24
+
25
+ /**
26
+ * Initialize appearance settings
27
+ * Business logic: Coordinate initialization process
28
+ */
29
+ async initialize(): Promise<void> {
30
+ // Prevent multiple initializations
31
+ if (this._isInitialized || this.initPromise) {
32
+ return this.initPromise || Promise.resolve();
33
+ }
34
+
35
+ this.initPromise = this._performInitialization();
36
+ return this.initPromise;
37
+ }
38
+
39
+ private async _performInitialization(): Promise<void> {
40
+ try {
41
+ if (__DEV__) {
42
+ console.log("[AppearanceService] Initializing appearance settings");
43
+ }
44
+
45
+ const savedSettings = await AppearanceStorage.getSettings();
46
+
47
+ if (savedSettings) {
48
+ // Load saved settings
49
+ useAppearanceStore.getState().setSettings(savedSettings);
50
+ useAppearanceStore.getState().setInitialized(true);
51
+
52
+ // Sync with design system theme
53
+ await this.syncWithDesignSystem(savedSettings);
54
+ } else {
55
+ // Use system theme as default, fallback to light
56
+ const systemTheme = getSystemTheme();
57
+ const defaultSettings: AppearanceSettings = {
58
+ themeMode: systemTheme || this.DEFAULT_THEME_MODE,
59
+ };
60
+
61
+ useAppearanceStore.getState().setSettings(defaultSettings);
62
+ useAppearanceStore.getState().setInitialized(true);
63
+
64
+ // Sync with design system theme
65
+ await this.syncWithDesignSystem(defaultSettings);
66
+ }
67
+
68
+ this._isInitialized = true;
69
+ } catch (error) {
70
+ if (__DEV__) {
71
+ console.error("[AppearanceService] Initialization failed:", error);
72
+ }
73
+
74
+ // Fallback to system theme or light mode on error
75
+ const systemTheme = getSystemTheme();
76
+ const fallbackSettings: AppearanceSettings = {
77
+ themeMode: systemTheme || this.DEFAULT_THEME_MODE,
78
+ };
79
+
80
+ useAppearanceStore.getState().setSettings(fallbackSettings);
81
+ useAppearanceStore.getState().setInitialized(true);
82
+
83
+ await this.syncWithDesignSystem(fallbackSettings);
84
+ this._isInitialized = true;
85
+ } finally {
86
+ this.initPromise = null;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Get current theme mode
92
+ * Business logic: Provide theme mode data
93
+ */
94
+ getThemeMode(): ThemeMode {
95
+ return useAppearanceStore.getState().settings.themeMode;
96
+ }
97
+
98
+ /**
99
+ * Set theme mode
100
+ * Business logic: Validate and apply theme mode
101
+ */
102
+ async setThemeMode(mode: ThemeMode): Promise<void> {
103
+ try {
104
+ if (__DEV__) {
105
+ console.log("[AppearanceService] Setting theme mode:", mode);
106
+ }
107
+
108
+ // Validate theme mode
109
+ if (!mode || (mode !== 'light' && mode !== 'dark')) {
110
+ throw new Error(`Invalid theme mode: ${mode}`);
111
+ }
112
+
113
+ const currentSettings = useAppearanceStore.getState().settings;
114
+ const newSettings: AppearanceSettings = {
115
+ ...currentSettings,
116
+ themeMode: mode,
117
+ };
118
+
119
+ // Update store
120
+ useAppearanceStore.getState().updateThemeMode(mode);
121
+
122
+ // Persist to storage
123
+ await AppearanceStorage.setSettings(newSettings);
124
+
125
+ // Sync with design system
126
+ await this.syncWithDesignSystem(newSettings);
127
+ } catch (error) {
128
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
129
+ if (__DEV__) {
130
+ console.error("[AppearanceService] Failed to set theme mode:", errorMessage);
131
+ }
132
+ throw error;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Toggle theme mode
138
+ * Business logic: Toggle between light and dark
139
+ */
140
+ async toggleTheme(): Promise<void> {
141
+ try {
142
+ const currentMode = this.getThemeMode();
143
+ const newMode: ThemeMode =
144
+ currentMode === "light" ? "dark" : "light";
145
+
146
+ await this.setThemeMode(newMode);
147
+ } catch (error) {
148
+ if (__DEV__) {
149
+ console.error("[AppearanceService] Failed to toggle theme:", error);
150
+ }
151
+ throw error;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Get custom colors
157
+ * Business logic: Provide custom colors data
158
+ */
159
+ getCustomColors(): CustomThemeColors | undefined {
160
+ return useAppearanceStore.getState().settings.customColors;
161
+ }
162
+
163
+ /**
164
+ * Set custom colors
165
+ * Business logic: Validate and apply custom colors
166
+ */
167
+ async setCustomColors(colors: CustomThemeColors): Promise<void> {
168
+ try {
169
+ if (__DEV__) {
170
+ console.log("[AppearanceService] Setting custom colors:", colors);
171
+ }
172
+
173
+ // Validate custom colors
174
+ const validation = validateAppearanceSettings({ customColors: colors });
175
+ if (!validation.isValid) {
176
+ throw new Error(`Invalid custom colors: ${validation.errors.join(', ')}`);
177
+ }
178
+
179
+ const currentSettings = useAppearanceStore.getState().settings;
180
+ const newSettings: AppearanceSettings = {
181
+ ...currentSettings,
182
+ customColors: {
183
+ ...currentSettings.customColors,
184
+ ...colors,
185
+ },
186
+ };
187
+
188
+ // Update store
189
+ useAppearanceStore.getState().updateCustomColors(newSettings.customColors);
190
+
191
+ // Persist to storage
192
+ await AppearanceStorage.setSettings(newSettings);
193
+
194
+ // Sync with design system
195
+ await this.syncWithDesignSystem(newSettings);
196
+ } catch (error) {
197
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
198
+ if (__DEV__) {
199
+ console.error("[AppearanceService] Failed to set custom colors:", errorMessage);
200
+ }
201
+ throw error;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Reset custom colors to defaults
207
+ * Business logic: Reset custom colors
208
+ */
209
+ async resetCustomColors(): Promise<void> {
210
+ try {
211
+ if (__DEV__) {
212
+ console.log("[AppearanceService] Resetting custom colors");
213
+ }
214
+
215
+ const currentSettings = useAppearanceStore.getState().settings;
216
+ const newSettings: AppearanceSettings = {
217
+ ...currentSettings,
218
+ customColors: undefined,
219
+ };
220
+
221
+ // Update store
222
+ useAppearanceStore.getState().updateCustomColors(undefined);
223
+
224
+ // Persist to storage
225
+ await AppearanceStorage.setSettings(newSettings);
226
+
227
+ // Sync with design System
228
+ await this.syncWithDesignSystem(newSettings);
229
+ } catch (error) {
230
+ if (__DEV__) {
231
+ console.error("[AppearanceService] Failed to reset custom colors:", error);
232
+ }
233
+ throw error;
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Reset all appearance settings
239
+ * Business logic: Reset all settings
240
+ */
241
+ async reset(): Promise<void> {
242
+ try {
243
+ if (__DEV__) {
244
+ console.log("[AppearanceService] Resetting all appearance settings");
245
+ }
246
+
247
+ // Clear storage
248
+ await AppearanceStorage.clear();
249
+
250
+ // Reset store to defaults
251
+ useAppearanceStore.getState().resetState();
252
+
253
+ // Reset design system theme to system preference
254
+ const systemTheme = getSystemTheme();
255
+ const defaultSettings: AppearanceSettings = {
256
+ themeMode: systemTheme || this.DEFAULT_THEME_MODE,
257
+ };
258
+
259
+ await this.syncWithDesignSystem(defaultSettings);
260
+ } catch (error) {
261
+ if (__DEV__) {
262
+ console.error("[AppearanceService] Failed to reset appearance:", error);
263
+ }
264
+ throw error;
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Check if appearance is initialized
270
+ * Business logic: Provide initialization status
271
+ */
272
+ isInitialized(): boolean {
273
+ return useAppearanceStore.getState().isInitialized;
274
+ }
275
+
276
+ /**
277
+ * Sync appearance settings with design system
278
+ * Private helper method
279
+ */
280
+ private async syncWithDesignSystem(settings: AppearanceSettings): Promise<void> {
281
+ try {
282
+ // Sync theme mode
283
+ useTheme.getState().setThemeMode(settings.themeMode);
284
+ useDesignSystemTheme.getState().setThemeMode(settings.themeMode);
285
+
286
+ // Sync custom colors
287
+ useDesignSystemTheme.getState().setCustomColors(settings.customColors);
288
+
289
+ if (__DEV__) {
290
+ console.log("[AppearanceService] Synced with design system:", settings);
291
+ }
292
+ } catch (error) {
293
+ if (__DEV__) {
294
+ console.error("[AppearanceService] Failed to sync with design system:", error);
295
+ }
296
+ throw error;
297
+ }
298
+ }
299
+ }
300
+
301
+ export const appearanceService = new AppearanceService();
@@ -0,0 +1,79 @@
1
+ /**
2
+ * System Theme Detection Utilities
3
+ *
4
+ * Utilities for detecting device theme preferences
5
+ */
6
+
7
+ import { Appearance, Platform } from 'react-native';
8
+ import type { ThemeMode } from '@umituz/react-native-design-system';
9
+
10
+ declare const window: any;
11
+
12
+ /**
13
+ * Get system theme mode from device settings
14
+ * @returns System theme mode ('light' | 'dark' | null)
15
+ */
16
+ export const getSystemTheme = (): ThemeMode | null => {
17
+ try {
18
+ // On web, use matchMedia
19
+ if (Platform.OS === 'web') {
20
+ if (typeof window !== 'undefined' && (window as any).matchMedia) {
21
+ const darkModeQuery = (window as any).matchMedia('(prefers-color-scheme: dark)');
22
+ if (darkModeQuery.matches) {
23
+ return 'dark';
24
+ }
25
+ return 'light';
26
+ }
27
+ }
28
+
29
+ // On native platforms, use Appearance API
30
+ const colorScheme = Appearance.getColorScheme();
31
+ return colorScheme === 'dark' ? 'dark' : 'light';
32
+ } catch (error) {
33
+ if (__DEV__) {
34
+ console.warn('[getSystemTheme] Failed to detect system theme:', error);
35
+ }
36
+ return null;
37
+ }
38
+ };
39
+
40
+ /**
41
+ * Check if system theme is dark
42
+ * @returns true if system prefers dark mode
43
+ */
44
+ export const isSystemThemeDark = (): boolean => {
45
+ return getSystemTheme() === 'dark';
46
+ };
47
+
48
+ /**
49
+ * Check if system theme is light
50
+ * @returns true if system prefers light mode
51
+ */
52
+ export const isSystemThemeLight = (): boolean => {
53
+ return getSystemTheme() === 'light';
54
+ };
55
+
56
+ /**
57
+ * Add system theme change listener
58
+ * @param callback - Function to call when theme changes
59
+ * @returns Cleanup function to remove listener
60
+ */
61
+ export const addSystemThemeListener = (
62
+ callback: (themeMode: ThemeMode) => void,
63
+ ): (() => void) => {
64
+ try {
65
+ const subscription = Appearance.addChangeListener(({ colorScheme }) => {
66
+ const themeMode: ThemeMode = colorScheme === 'dark' ? 'dark' : 'light';
67
+ callback(themeMode);
68
+ });
69
+
70
+ return () => {
71
+ subscription?.remove();
72
+ };
73
+ } catch (error) {
74
+ if (__DEV__) {
75
+ console.warn('[addSystemThemeListener] Failed to add listener:', error);
76
+ }
77
+ return () => { }; // Return empty cleanup function
78
+ }
79
+ };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Appearance Validation Utilities
3
+ *
4
+ * Validation functions for appearance settings and colors
5
+ */
6
+
7
+ import type { ThemeMode, CustomThemeColors } from '@umituz/react-native-design-system';
8
+
9
+ /**
10
+ * Validate theme mode
11
+ * @param mode - Theme mode to validate
12
+ * @returns true if valid theme mode
13
+ */
14
+ export const isValidThemeMode = (mode: string): mode is ThemeMode => {
15
+ return mode === 'light' || mode === 'dark';
16
+ };
17
+
18
+ /**
19
+ * Validate hex color format
20
+ * @param color - Color string to validate
21
+ * @returns true if valid hex color
22
+ */
23
+ export const isValidHexColor = (color: string): boolean => {
24
+ if (!color || typeof color !== 'string') {
25
+ return false;
26
+ }
27
+
28
+ const hexRegex = /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/;
29
+ return hexRegex.test(color);
30
+ };
31
+
32
+ /**
33
+ * Validate custom colors object
34
+ * @param colors - Custom colors to validate
35
+ * @returns Validation result with errors
36
+ */
37
+ export const validateCustomColors = (
38
+ colors: CustomThemeColors
39
+ ): { isValid: boolean; errors: string[] } => {
40
+ const errors: string[] = [];
41
+
42
+ // Check each color if provided
43
+ const colorFields: (keyof CustomThemeColors)[] = [
44
+ 'primary', 'primaryLight', 'primaryDark',
45
+ 'secondary', 'secondaryLight', 'secondaryDark',
46
+ 'accent', 'accentLight', 'accentDark',
47
+ 'buttonPrimary', 'buttonSecondary'
48
+ ];
49
+
50
+ for (const field of colorFields) {
51
+ const color = colors[field];
52
+ if (color && !isValidHexColor(color)) {
53
+ errors.push(`Invalid ${field} color: ${color}`);
54
+ }
55
+ }
56
+
57
+ return {
58
+ isValid: errors.length === 0,
59
+ errors
60
+ };
61
+ };
62
+
63
+ /**
64
+ * Validate appearance settings
65
+ * @param settings - Settings to validate
66
+ * @returns Validation result with errors
67
+ */
68
+ export const validateAppearanceSettings = (settings: {
69
+ themeMode?: string;
70
+ customColors?: CustomThemeColors;
71
+ }): { isValid: boolean; errors: string[] } => {
72
+ const errors: string[] = [];
73
+
74
+ // Validate theme mode
75
+ if (settings.themeMode && !isValidThemeMode(settings.themeMode)) {
76
+ errors.push(`Invalid theme mode: ${settings.themeMode}`);
77
+ }
78
+
79
+ // Validate custom colors
80
+ if (settings.customColors) {
81
+ const colorValidation = validateCustomColors(settings.customColors);
82
+ if (!colorValidation.isValid) {
83
+ errors.push(...colorValidation.errors);
84
+ }
85
+ }
86
+
87
+ return {
88
+ isValid: errors.length === 0,
89
+ errors
90
+ };
91
+ };