@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,408 @@
1
+ /**
2
+ * Tests for utility functions
3
+ */
4
+ import '../../types/global.d.ts';
5
+ import { Linking } from 'react-native';
6
+ import {
7
+ createDefaultConfig,
8
+ validateConfig,
9
+ mergeConfigs,
10
+ isValidEmail,
11
+ isValidUrl,
12
+ openUrl,
13
+ sendEmail,
14
+ } from '../index';
15
+
16
+ // Mock Linking
17
+ jest.mock('react-native', () => ({
18
+ Linking: {
19
+ canOpenURL: jest.fn(),
20
+ openURL: jest.fn(),
21
+ },
22
+ }));
23
+
24
+ // Mock console methods
25
+ jest.spyOn(console, 'log').mockImplementation();
26
+ const mockConsoleError = jest.spyOn(console, 'error').mockImplementation();
27
+
28
+ describe('Utils', () => {
29
+ beforeEach(() => {
30
+ jest.clearAllMocks();
31
+ });
32
+
33
+ describe('createDefaultConfig', () => {
34
+ it('should create default config', () => {
35
+ const config = createDefaultConfig();
36
+
37
+ expect(config).toEqual({
38
+ appInfo: {
39
+ name: '',
40
+ version: '1.0.0',
41
+ description: '',
42
+ developer: '',
43
+ contactEmail: '',
44
+ websiteUrl: '',
45
+ websiteDisplay: '',
46
+ moreAppsUrl: '',
47
+ privacyPolicyUrl: '',
48
+ termsOfServiceUrl: '',
49
+ },
50
+ theme: {
51
+ primary: '#007AFF',
52
+ secondary: '#5856D6',
53
+ background: '#FFFFFF',
54
+ text: '#000000',
55
+ border: '#E5E5E5',
56
+ },
57
+ style: {
58
+ containerStyle: {},
59
+ itemStyle: {},
60
+ textStyle: {},
61
+ iconStyle: {},
62
+ },
63
+ actions: {
64
+ onWebsitePress: undefined,
65
+ onEmailPress: undefined,
66
+ onPrivacyPress: undefined,
67
+ onTermsPress: undefined,
68
+ onMoreAppsPress: undefined,
69
+ },
70
+ });
71
+ });
72
+
73
+ it('should merge with overrides', () => {
74
+ const overrides = {
75
+ appInfo: {
76
+ name: 'Test App',
77
+ version: '2.0.0',
78
+ },
79
+ theme: {
80
+ primary: '#FF0000',
81
+ },
82
+ };
83
+
84
+ const config = createDefaultConfig(overrides);
85
+
86
+ expect(config.appInfo.name).toBe('Test App');
87
+ expect(config.appInfo.version).toBe('2.0.0');
88
+ expect(config.appInfo.description).toBe(''); // Should keep default
89
+ expect(config.theme.primary).toBe('#FF0000');
90
+ expect(config.theme.secondary).toBe('#5856D6'); // Should keep default
91
+ });
92
+
93
+ it('should handle empty overrides', () => {
94
+ const config = createDefaultConfig({});
95
+
96
+ expect(config.appInfo.name).toBe('');
97
+ expect(config.theme.primary).toBe('#007AFF');
98
+ });
99
+
100
+ it('should handle null/undefined overrides', () => {
101
+ const config = createDefaultConfig(null as unknown);
102
+
103
+ expect(config.appInfo.name).toBe('');
104
+ expect(config.theme.primary).toBe('#007AFF');
105
+ });
106
+ });
107
+
108
+ describe('validateConfig', () => {
109
+ it('should validate valid config', () => {
110
+ const validConfig = {
111
+ appInfo: {
112
+ name: 'Test App',
113
+ version: '1.0.0',
114
+ },
115
+ };
116
+
117
+ expect(validateConfig(validConfig)).toBe(true);
118
+ });
119
+
120
+ it('should reject invalid config', () => {
121
+ expect(validateConfig(null)).toBe(false);
122
+ expect(validateConfig(undefined)).toBe(false);
123
+ expect(validateConfig('invalid')).toBe(false);
124
+ expect(validateConfig(123)).toBe(false);
125
+ expect(validateConfig([])).toBe(false);
126
+ });
127
+
128
+ it('should reject config without appInfo', () => {
129
+ const configWithoutAppInfo = {
130
+ theme: {
131
+ primary: '#FF0000',
132
+ },
133
+ };
134
+
135
+ expect(validateConfig(configWithoutAppInfo)).toBe(false);
136
+ });
137
+
138
+ it('should reject config with invalid appInfo', () => {
139
+ const configWithInvalidAppInfo = {
140
+ appInfo: 'invalid',
141
+ };
142
+
143
+ expect(validateConfig(configWithInvalidAppInfo)).toBe(false);
144
+ });
145
+
146
+ it('should accept config with empty appInfo', () => {
147
+ const configWithEmptyAppInfo = {
148
+ appInfo: {},
149
+ };
150
+
151
+ expect(validateConfig(configWithEmptyAppInfo)).toBe(true);
152
+ });
153
+ });
154
+
155
+ describe('mergeConfigs', () => {
156
+ it('should merge multiple configs', () => {
157
+ const config1 = { appInfo: { name: 'App1' } };
158
+ const config2 = { appInfo: { version: '1.0.0' } };
159
+ const config3 = { theme: { primary: '#FF0000' } };
160
+
161
+ const merged = mergeConfigs(config1, config2, config3);
162
+
163
+ expect(merged).toEqual({
164
+ appInfo: {
165
+ name: 'App1',
166
+ version: '1.0.0',
167
+ },
168
+ theme: {
169
+ primary: '#FF0000',
170
+ },
171
+ });
172
+ });
173
+
174
+ it('should handle empty configs', () => {
175
+ const merged = mergeConfigs();
176
+
177
+ expect(merged).toEqual({});
178
+ });
179
+
180
+ it('should handle null/undefined configs', () => {
181
+ const config1 = { appInfo: { name: 'App1' } };
182
+
183
+ const merged = mergeConfigs(config1, null, undefined, {} as unknown);
184
+
185
+ expect(merged).toEqual({
186
+ appInfo: {
187
+ name: 'App1',
188
+ },
189
+ });
190
+ });
191
+
192
+ it('should override with later configs', () => {
193
+ const config1 = { appInfo: { name: 'App1', version: '1.0.0' } };
194
+ const config2 = { appInfo: { name: 'App2' } };
195
+
196
+ const merged = mergeConfigs(config1, config2);
197
+
198
+ expect(merged.appInfo.name).toBe('App2');
199
+ expect(merged.appInfo.version).toBe('1.0.0');
200
+ });
201
+ });
202
+
203
+ describe('isValidEmail', () => {
204
+ it('should validate valid emails', () => {
205
+ expect(isValidEmail('test@example.com')).toBe(true);
206
+ expect(isValidEmail('user.name@domain.co.uk')).toBe(true);
207
+ expect(isValidEmail('user+tag@example.org')).toBe(true);
208
+ expect(isValidEmail('user123@test-domain.com')).toBe(true);
209
+ });
210
+
211
+ it('should reject invalid emails', () => {
212
+ expect(isValidEmail('')).toBe(false);
213
+ expect(isValidEmail('invalid')).toBe(false);
214
+ expect(isValidEmail('test@')).toBe(false);
215
+ expect(isValidEmail('@example.com')).toBe(false);
216
+ expect(isValidEmail('test@.com')).toBe(false);
217
+ expect(isValidEmail('test@example')).toBe(false);
218
+ expect(isValidEmail('test..test@example.com')).toBe(false);
219
+ });
220
+
221
+ it('should handle edge cases', () => {
222
+ expect(isValidEmail('a@b.c')).toBe(true);
223
+ expect(isValidEmail('test@example.c')).toBe(true);
224
+ expect(isValidEmail('test@123.456.789.0')).toBe(true);
225
+ });
226
+ });
227
+
228
+ describe('isValidUrl', () => {
229
+ it('should validate valid URLs', () => {
230
+ expect(isValidUrl('https://example.com')).toBe(true);
231
+ expect(isValidUrl('http://example.com')).toBe(true);
232
+ expect(isValidUrl('https://www.example.com')).toBe(true);
233
+ expect(isValidUrl('https://example.com/path')).toBe(true);
234
+ expect(isValidUrl('https://example.com/path?query=value')).toBe(true);
235
+ expect(isValidUrl('https://example.com/path#fragment')).toBe(true);
236
+ });
237
+
238
+ it('should reject invalid URLs', () => {
239
+ expect(isValidUrl('')).toBe(false);
240
+ expect(isValidUrl('not-a-url')).toBe(false);
241
+ expect(isValidUrl('example.com')).toBe(false);
242
+ expect(isValidUrl('ftp://example.com')).toBe(false);
243
+ expect(isValidUrl('://example.com')).toBe(false);
244
+ });
245
+
246
+ it('should handle edge cases', () => {
247
+ expect(isValidUrl('http://localhost')).toBe(true);
248
+ expect(isValidUrl('https://192.168.1.1')).toBe(true);
249
+ expect(isValidUrl('https://example.com:8080')).toBe(true);
250
+ });
251
+ });
252
+
253
+ describe('openUrl', () => {
254
+
255
+ beforeEach(() => {
256
+ Linking.canOpenURL.mockClear();
257
+ Linking.openURL.mockClear();
258
+ });
259
+
260
+ it('should open valid URL in browser environment', async () => {
261
+ // Mock window.open directly since jsdom already provides window
262
+ const originalOpen = window.open;
263
+ const mockOpen = jest.fn().mockReturnValue(true);
264
+ window.open = mockOpen;
265
+
266
+ const result = await openUrl('https://example.com');
267
+
268
+ expect(mockOpen).toHaveBeenCalledWith('https://example.com', '_blank');
269
+ expect(result).toBe(true);
270
+
271
+ // Restore original
272
+ window.open = originalOpen;
273
+ });
274
+
275
+ it('should open valid URL in React Native environment', async () => {
276
+ // Remove window object to force React Native path
277
+ const originalWindow = global.window;
278
+ delete global.window;
279
+
280
+ Linking.canOpenURL.mockResolvedValue(true);
281
+ Linking.openURL.mockResolvedValue();
282
+
283
+ const result = await openUrl('https://example.com');
284
+
285
+ expect(Linking.canOpenURL).toHaveBeenCalledWith('https://example.com');
286
+ expect(Linking.openURL).toHaveBeenCalledWith('https://example.com');
287
+ expect(result).toBe(true);
288
+
289
+ global.window = originalWindow;
290
+ });
291
+
292
+ it('should handle URL that cannot be opened', async () => {
293
+ // Remove window object to force React Native path
294
+ const originalWindow = global.window;
295
+ delete global.window;
296
+
297
+ Linking.canOpenURL.mockResolvedValue(false);
298
+
299
+ const result = await openUrl('https://example.com');
300
+
301
+ expect(Linking.canOpenURL).toHaveBeenCalledWith('https://example.com');
302
+ expect(Linking.openURL).not.toHaveBeenCalled();
303
+ expect(result).toBe(false);
304
+
305
+ global.window = originalWindow;
306
+ });
307
+
308
+ it('should handle errors', async () => {
309
+ // Remove window object to force React Native path
310
+ const originalWindow = global.window;
311
+ delete global.window;
312
+
313
+ Linking.canOpenURL.mockResolvedValue(true);
314
+ Linking.openURL.mockRejectedValue(new Error('Failed to open'));
315
+
316
+ const result = await openUrl('https://example.com');
317
+
318
+ expect(result).toBe(false);
319
+ expect(mockConsoleError).toHaveBeenCalledWith('Failed to open URL:', expect.any(Error));
320
+
321
+ global.window = originalWindow;
322
+ });
323
+
324
+ it('should log errors in development', async () => {
325
+ const originalDev = global.__DEV__;
326
+ global.__DEV__ = true;
327
+
328
+ // Remove window object to force React Native path
329
+ const originalWindow = global.window;
330
+ delete global.window;
331
+
332
+ Linking.canOpenURL.mockResolvedValue(true);
333
+ Linking.openURL.mockRejectedValue(new Error('Failed to open'));
334
+
335
+ await openUrl('https://example.com');
336
+
337
+ expect(mockConsoleError).toHaveBeenCalledWith('Failed to open URL:', expect.any(Error));
338
+
339
+ global.__DEV__ = originalDev;
340
+ global.window = originalWindow;
341
+ });
342
+ });
343
+
344
+ describe('sendEmail', () => {
345
+
346
+
347
+ beforeEach(() => {
348
+ Linking.canOpenURL.mockClear();
349
+ Linking.openURL.mockClear();
350
+ });
351
+
352
+ it('should send email without subject', async () => {
353
+ Linking.canOpenURL.mockResolvedValue(true);
354
+ Linking.openURL.mockResolvedValue();
355
+
356
+ const result = await sendEmail('test@example.com');
357
+
358
+ expect(Linking.canOpenURL).toHaveBeenCalledWith('mailto:test@example.com');
359
+ expect(Linking.openURL).toHaveBeenCalledWith('mailto:test@example.com');
360
+ expect(result).toBe(true);
361
+ });
362
+
363
+ it('should send email with subject', async () => {
364
+ Linking.canOpenURL.mockResolvedValue(true);
365
+ Linking.openURL.mockResolvedValue();
366
+
367
+ const result = await sendEmail('test@example.com', 'Test Subject');
368
+
369
+ expect(Linking.canOpenURL).toHaveBeenCalledWith('mailto:test@example.com?subject=Test%20Subject');
370
+ expect(Linking.openURL).toHaveBeenCalledWith('mailto:test@example.com?subject=Test%20Subject');
371
+ expect(result).toBe(true);
372
+ });
373
+
374
+ it('should handle email that cannot be sent', async () => {
375
+ Linking.canOpenURL.mockResolvedValue(false);
376
+
377
+ const result = await sendEmail('test@example.com');
378
+
379
+ expect(Linking.canOpenURL).toHaveBeenCalledWith('mailto:test@example.com');
380
+ expect(Linking.openURL).not.toHaveBeenCalled();
381
+ expect(result).toBe(false);
382
+ });
383
+
384
+ it('should handle errors', async () => {
385
+ Linking.canOpenURL.mockResolvedValue(true);
386
+ Linking.openURL.mockRejectedValue(new Error('Failed to send email'));
387
+
388
+ const result = await sendEmail('test@example.com');
389
+
390
+ expect(result).toBe(false);
391
+ expect(mockConsoleError).toHaveBeenCalledWith('Failed to send email:', expect.any(Error));
392
+ });
393
+
394
+ it('should log errors in development', async () => {
395
+ const originalDev = global.__DEV__;
396
+ global.__DEV__ = true;
397
+
398
+ Linking.canOpenURL.mockResolvedValue(true);
399
+ Linking.openURL.mockRejectedValue(new Error('Failed to send email'));
400
+
401
+ await sendEmail('test@example.com');
402
+
403
+ expect(mockConsoleError).toHaveBeenCalledWith('Failed to send email:', expect.any(Error));
404
+
405
+ global.__DEV__ = originalDev;
406
+ });
407
+ });
408
+ });
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Utility functions for About package
3
+ * General purpose utilities for all applications
4
+ */
5
+
6
+ /**
7
+ * Create default configuration with overrides
8
+ */
9
+ export const createDefaultConfig = (overrides: Record<string, unknown> = {}) => {
10
+ if (!overrides || typeof overrides !== 'object') {
11
+ overrides = {};
12
+ }
13
+ const overridesObj = overrides as Record<string, Record<string, unknown>>;
14
+ return {
15
+ appInfo: {
16
+ name: '',
17
+ version: '1.0.0',
18
+ description: '',
19
+ developer: '',
20
+ contactEmail: '',
21
+ websiteUrl: '',
22
+ websiteDisplay: '',
23
+ moreAppsUrl: '',
24
+ privacyPolicyUrl: '',
25
+ termsOfServiceUrl: '',
26
+ ...(overridesObj.appInfo || {}),
27
+ },
28
+ theme: {
29
+ primary: '#007AFF',
30
+ secondary: '#5856D6',
31
+ background: '#FFFFFF',
32
+ text: '#000000',
33
+ border: '#E5E5E5',
34
+ ...(overridesObj.theme || {}),
35
+ },
36
+ style: {
37
+ containerStyle: {},
38
+ itemStyle: {},
39
+ textStyle: {},
40
+ iconStyle: {},
41
+ ...(overridesObj.style || {}),
42
+ },
43
+ actions: {
44
+ onWebsitePress: undefined,
45
+ onEmailPress: undefined,
46
+ onPrivacyPress: undefined,
47
+ onTermsPress: undefined,
48
+ onMoreAppsPress: undefined,
49
+ ...(overridesObj.actions || {}),
50
+ },
51
+ };
52
+ };
53
+
54
+ /**
55
+ * Validate configuration object
56
+ */
57
+ export const validateConfig = (config: unknown): boolean => {
58
+ if (!config || typeof config !== 'object') {
59
+ return false;
60
+ }
61
+
62
+ const configObj = config as Record<string, unknown>;
63
+ if (!configObj.appInfo || typeof configObj.appInfo !== 'object') {
64
+ return false;
65
+ }
66
+
67
+ return true;
68
+ };
69
+
70
+ /**
71
+ * Merge multiple configurations
72
+ */
73
+ export const mergeConfigs = (...configs: Record<string, unknown>[]) => {
74
+ const result: Record<string, unknown> = {};
75
+
76
+ for (const config of configs.filter(Boolean)) {
77
+ if (config && typeof config === 'object') {
78
+ for (const [key, value] of Object.entries(config)) {
79
+ if (value && typeof value === 'object' && !Array.isArray(value) && result[key] && typeof result[key] === 'object' && !Array.isArray(result[key])) {
80
+ // Deep merge for nested objects
81
+ result[key] = { ...result[key] as Record<string, unknown>, ...value as Record<string, unknown> };
82
+ } else {
83
+ result[key] = value;
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ return result;
90
+ };
91
+
92
+ /**
93
+ * Validate email format
94
+ */
95
+ export const isValidEmail = (email: string): boolean => {
96
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
97
+ // Additional check to prevent consecutive dots
98
+ return emailRegex.test(email) && !email.includes('..');
99
+ };
100
+
101
+ /**
102
+ * Validate URL format
103
+ */
104
+ export const isValidUrl = (url: string): boolean => {
105
+ try {
106
+ const urlObj = new URL(url);
107
+ // Only allow http and https protocols
108
+ return (urlObj as any).protocol === 'http:' || (urlObj as any).protocol === 'https:';
109
+ } catch {
110
+ return false;
111
+ }
112
+ };
113
+
114
+ /**
115
+ * Open URL in external browser
116
+ */
117
+ export const openUrl = async (url: string): Promise<boolean> => {
118
+ try {
119
+ const { Linking } = await import('react-native');
120
+ const canOpen = await Linking.canOpenURL(url);
121
+
122
+ if (canOpen) {
123
+ await Linking.openURL(url);
124
+ return true;
125
+ }
126
+
127
+ return false;
128
+ } catch (error) {
129
+ if (__DEV__) {
130
+ console.error('Failed to open URL:', error);
131
+ }
132
+ return false;
133
+ }
134
+ };
135
+
136
+ /**
137
+ * Send email
138
+ */
139
+ export const sendEmail = async (email: string, subject?: string): Promise<boolean> => {
140
+ try {
141
+ const { Linking } = await import('react-native');
142
+ const url = subject
143
+ ? `mailto:${email}?subject=${encodeURIComponent(subject)}`
144
+ : `mailto:${email}`;
145
+
146
+ const canOpen = await Linking.canOpenURL(url);
147
+
148
+ if (canOpen) {
149
+ await Linking.openURL(url);
150
+ return true;
151
+ }
152
+
153
+ return false;
154
+ } catch (error) {
155
+ if (__DEV__) {
156
+ console.error('Failed to send email:', error);
157
+ }
158
+ return false;
159
+ }
160
+ };