@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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@umituz/react-native-settings",
3
- "version": "4.17.14",
4
- "description": "Settings management for React Native apps - user preferences, theme, language, notifications",
3
+ "version": "4.17.16",
4
+ "description": "Complete settings hub for React Native apps - consolidated package with settings, about, legal, appearance, feedback, and FAQs",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
7
7
  "scripts": {
@@ -18,7 +18,13 @@
18
18
  "user-settings",
19
19
  "theme",
20
20
  "language",
21
- "notifications"
21
+ "notifications",
22
+ "about",
23
+ "legal",
24
+ "appearance",
25
+ "feedback",
26
+ "faqs",
27
+ "consolidated"
22
28
  ],
23
29
  "author": "Ümit UZ <umit@umituz.com>",
24
30
  "license": "MIT",
@@ -30,13 +36,9 @@
30
36
  "@expo/vector-icons": ">=14.0.0",
31
37
  "@react-navigation/native": ">=6.0.0",
32
38
  "@react-navigation/stack": ">=6.0.0",
33
- "@umituz/react-native-about": "latest",
34
- "@umituz/react-native-appearance": "latest",
35
39
  "@umituz/react-native-auth": "latest",
36
40
  "@umituz/react-native-avatar": "latest",
37
- "@umituz/react-native-faqs": "latest",
38
- "@umituz/react-native-feedback": "latest",
39
- "@umituz/react-native-legal": "latest",
41
+ "@umituz/react-native-design-system": "latest",
40
42
  "@umituz/react-native-localization": "latest",
41
43
  "@umituz/react-native-notifications": "latest",
42
44
  "@umituz/react-native-onboarding": "latest",
@@ -53,13 +55,9 @@
53
55
  "@expo/vector-icons": "^15.0.0",
54
56
  "@types/jest": "^29.5.14",
55
57
  "@types/react": "~19.1.10",
56
- "@umituz/react-native-about": "latest",
57
- "@umituz/react-native-appearance": "latest",
58
58
  "@umituz/react-native-auth": "latest",
59
59
  "@umituz/react-native-avatar": "latest",
60
- "@umituz/react-native-faqs": "latest",
61
- "@umituz/react-native-feedback": "latest",
62
- "@umituz/react-native-legal": "latest",
60
+ "@umituz/react-native-design-system": "latest",
63
61
  "@umituz/react-native-localization": "latest",
64
62
  "@umituz/react-native-notifications": "latest",
65
63
  "@umituz/react-native-onboarding": "latest",
@@ -76,5 +74,8 @@
76
74
  "src",
77
75
  "README.md",
78
76
  "LICENSE"
79
- ]
80
- }
77
+ ],
78
+ "dependencies": {
79
+ "@umituz/react-native-design-system": "^2.2.0"
80
+ }
81
+ }
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Integration tests for the entire About package
3
+ */
4
+ import '../types/global.d.ts';
5
+ import './types.d.ts';
6
+ import React from 'react';
7
+ import { View, Text } from 'react-native';
8
+ import { render, waitFor, fireEvent } from '@testing-library/react';
9
+ import { AboutScreen } from '../presentation/screens/AboutScreen';
10
+ import { AboutConfig } from '../domain/entities/AppInfo';
11
+
12
+ // Mock console methods
13
+ const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation();
14
+ const mockConsoleError = jest.spyOn(console, 'error').mockImplementation();
15
+
16
+ describe('About Package Integration', () => {
17
+ const fullConfig: AboutConfig = {
18
+ appInfo: {
19
+ name: 'Integration Test App',
20
+ version: '2.0.0',
21
+ description: 'This is an integration test app',
22
+ developer: 'Test Developer',
23
+ contactEmail: 'integration@test.com',
24
+ websiteUrl: 'https://integration.example.com',
25
+ websiteDisplay: 'integration.example.com',
26
+ moreAppsUrl: 'https://apps.integration.example.com',
27
+ },
28
+ theme: {
29
+ primary: '#FF0000',
30
+ secondary: '#00FF00',
31
+ background: '#0000FF',
32
+ text: '#FFFFFF',
33
+ border: '#CCCCCC',
34
+ },
35
+ style: {
36
+ containerStyle: { backgroundColor: '#f0f0f0' },
37
+ itemStyle: { padding: 20 },
38
+ textStyle: { fontSize: 18 },
39
+ iconStyle: { color: '#333333' },
40
+ },
41
+ actions: {
42
+ onWebsitePress: jest.fn(),
43
+ onEmailPress: jest.fn(),
44
+ onMoreAppsPress: jest.fn(),
45
+ },
46
+ };
47
+
48
+ beforeEach(() => {
49
+ jest.clearAllMocks();
50
+ });
51
+
52
+ afterEach(() => {
53
+ mockConsoleLog.mockClear();
54
+ mockConsoleError.mockClear();
55
+ });
56
+
57
+ describe('Complete Flow', () => {
58
+ it('should render complete about screen with all features', async () => {
59
+ const { getByText, queryByText } = render(
60
+ <AboutScreen config={fullConfig} />
61
+ );
62
+
63
+ // Should show loading initially
64
+ expect(getByText((content) => content.includes('Loading'))).toBeTruthy();
65
+
66
+ // Wait for content to load
67
+ await waitFor(() => {
68
+ expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
69
+ });
70
+
71
+ // Check header content
72
+ expect(getByText('Integration Test App')).toBeTruthy();
73
+ expect(getByText('Version 2.0.0')).toBeTruthy();
74
+ expect(getByText('This is an integration test app')).toBeTruthy();
75
+
76
+ // Check content items
77
+ expect(getByText('Developer')).toBeTruthy();
78
+ expect(getByText('Test Developer')).toBeTruthy();
79
+ expect(getByText('Contact')).toBeTruthy();
80
+ expect(getByText('integration@test.com')).toBeTruthy();
81
+ expect(getByText('Website')).toBeTruthy();
82
+ expect(getByText('integration.example.com')).toBeTruthy();
83
+ expect(getByText('More Apps')).toBeTruthy();
84
+ });
85
+
86
+ it('should handle all user interactions', async () => {
87
+ const { getByTestId } = render(
88
+ <AboutScreen config={fullConfig} />
89
+ );
90
+
91
+ // Wait for content to load
92
+ await waitFor(() => {
93
+ expect(getByTestId('email-item')).toBeTruthy();
94
+ });
95
+
96
+ // Test all interactions
97
+ fireEvent.click(getByTestId('email-item'));
98
+ fireEvent.click(getByTestId('website-item'));
99
+ fireEvent.click(getByTestId('more-apps-item'));
100
+
101
+ // Verify all actions were called
102
+ expect(fullConfig.actions!.onEmailPress).toHaveBeenCalledTimes(1);
103
+ expect(fullConfig.actions!.onWebsitePress).toHaveBeenCalledTimes(1);
104
+ expect(fullConfig.actions!.onMoreAppsPress).toHaveBeenCalledTimes(1);
105
+ });
106
+ });
107
+
108
+ describe('Minimal Configuration', () => {
109
+ it('should work with minimal config', async () => {
110
+ const minimalConfig: AboutConfig = {
111
+ appInfo: {
112
+ name: 'Minimal App',
113
+ version: '1.0.0',
114
+ },
115
+ };
116
+
117
+ const { getByText, queryByText } = render(
118
+ <AboutScreen config={minimalConfig} />
119
+ );
120
+
121
+ await waitFor(() => {
122
+ expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
123
+ });
124
+
125
+ // Should show basic info
126
+ expect(getByText('Minimal App')).toBeTruthy();
127
+ expect(getByText('Version 1.0.0')).toBeTruthy();
128
+
129
+ // Should not show optional items
130
+ expect(queryByText('Developer')).toBeFalsy();
131
+ expect(queryByText('Contact')).toBeFalsy();
132
+ expect(queryByText('Website')).toBeFalsy();
133
+ });
134
+ });
135
+
136
+ describe('Custom Components', () => {
137
+ it('should render custom header and footer', async () => {
138
+ const CustomHeader = () => <View testID="custom-header"><Text>Custom Header</Text></View>;
139
+ const CustomFooter = () => <View testID="custom-footer"><Text>Custom Footer</Text></View>;
140
+
141
+ const { getByTestId, queryByText } = render(
142
+ <AboutScreen
143
+ config={fullConfig}
144
+ headerComponent={<CustomHeader />}
145
+ footerComponent={<CustomFooter />}
146
+ showHeader={false}
147
+ />
148
+ );
149
+
150
+ await waitFor(() => {
151
+ expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
152
+ });
153
+
154
+ // Should show custom components
155
+ expect(getByTestId('custom-header')).toBeTruthy();
156
+ expect(getByTestId('custom-footer')).toBeTruthy();
157
+
158
+ // Should not show default header
159
+ expect(queryByText('Integration Test App')).toBeFalsy();
160
+ expect(queryByText('Version 2.0.0')).toBeFalsy();
161
+ });
162
+ });
163
+
164
+ describe('Error Handling', () => {
165
+ it('should handle invalid config gracefully', async () => {
166
+ const { queryByText, getByText } = render(
167
+ <AboutScreen config={null as unknown} />
168
+ );
169
+
170
+ await waitFor(() => {
171
+ expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
172
+ });
173
+
174
+ expect(getByText('No app information available')).toBeTruthy();
175
+ });
176
+
177
+ it('should handle missing actions gracefully', async () => {
178
+ const configWithoutActions: AboutConfig = {
179
+ appInfo: fullConfig.appInfo,
180
+ };
181
+
182
+ const { getByTestId } = render(
183
+ <AboutScreen config={configWithoutActions} />
184
+ );
185
+
186
+ await waitFor(() => {
187
+ expect(getByTestId('email-item')).toBeTruthy();
188
+ });
189
+
190
+ // Should not crash when pressing items without actions
191
+ expect(() => {
192
+ fireEvent.click(getByTestId('email-item'));
193
+ fireEvent.click(getByTestId('website-item'));
194
+ }).not.toThrow();
195
+ });
196
+ });
197
+
198
+ describe('Performance', () => {
199
+ it('should handle rapid re-renders', async () => {
200
+ const { rerender, getByText, queryByText } = render(
201
+ <AboutScreen config={fullConfig} />
202
+ );
203
+
204
+ await waitFor(() => {
205
+ expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
206
+ });
207
+
208
+ // Rapid re-renders with different configs
209
+ for (let i = 0; i < 5; i++) {
210
+ const newConfig = {
211
+ ...fullConfig,
212
+ appInfo: {
213
+ ...fullConfig.appInfo,
214
+ name: `App ${i}`,
215
+ },
216
+ };
217
+
218
+ rerender(<AboutScreen config={newConfig} />);
219
+
220
+ await waitFor(() => {
221
+ expect(getByText(`App ${i}`)).toBeTruthy();
222
+ });
223
+ }
224
+
225
+ // Should not crash and should show final state
226
+ expect(getByText('App 4')).toBeTruthy();
227
+ });
228
+
229
+ it('should handle prop changes efficiently', async () => {
230
+ const { rerender, getByText, queryByText } = render(
231
+ <AboutScreen config={fullConfig} />
232
+ );
233
+
234
+ await waitFor(() => {
235
+ expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
236
+ });
237
+
238
+ // Change showHeader prop
239
+ rerender(<AboutScreen config={fullConfig} showHeader={false} />);
240
+
241
+ await waitFor(() => {
242
+ expect(queryByText('Integration Test App')).toBeFalsy();
243
+ });
244
+
245
+ // Change back
246
+ rerender(<AboutScreen config={fullConfig} showHeader={true} />);
247
+
248
+ await waitFor(() => {
249
+ expect(getByText('Integration Test App')).toBeTruthy();
250
+ });
251
+ });
252
+ });
253
+
254
+ describe('Memory Management', () => {
255
+ it('should cleanup properly on unmount', async () => {
256
+ const { unmount, queryByText } = render(
257
+ <AboutScreen config={fullConfig} />
258
+ );
259
+
260
+ await waitFor(() => {
261
+ expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
262
+ });
263
+
264
+ // Should not throw when unmounting
265
+ expect(() => {
266
+ unmount();
267
+ }).not.toThrow();
268
+ });
269
+
270
+ it('should handle unmount during loading', () => {
271
+ const { unmount } = render(
272
+ <AboutScreen config={fullConfig} />
273
+ );
274
+
275
+ // Should not throw when unmounting during loading
276
+ expect(() => {
277
+ unmount();
278
+ }).not.toThrow();
279
+ });
280
+ });
281
+
282
+ describe('Accessibility', () => {
283
+ it('should have proper test IDs', async () => {
284
+ const { getByTestId } = render(
285
+ <AboutScreen config={fullConfig} testID="about-screen" />
286
+ );
287
+
288
+ await waitFor(() => {
289
+ expect(getByTestId('about-screen')).toBeTruthy();
290
+ });
291
+
292
+ // Check for item test IDs
293
+ expect(getByTestId('developer-item')).toBeTruthy();
294
+ expect(getByTestId('email-item')).toBeTruthy();
295
+ expect(getByTestId('website-item')).toBeTruthy();
296
+ expect(getByTestId('more-apps-item')).toBeTruthy();
297
+ });
298
+ });
299
+
300
+ describe('Console Logging', () => {
301
+ it('should log in development mode', async () => {
302
+ const originalDev = global.__DEV__;
303
+ global.__DEV__ = true;
304
+
305
+ render(<AboutScreen config={fullConfig} />);
306
+
307
+ await waitFor(() => {
308
+ expect(mockConsoleLog).toHaveBeenCalled();
309
+ });
310
+
311
+ global.__DEV__ = originalDev;
312
+ });
313
+
314
+ it('should not log in production mode', async () => {
315
+ const originalDev = global.__DEV__;
316
+ global.__DEV__ = false;
317
+
318
+ render(<AboutScreen config={fullConfig} />);
319
+
320
+ await waitFor(() => {
321
+ // Wait a bit to ensure no logging
322
+ expect(mockConsoleLog).not.toHaveBeenCalled();
323
+ });
324
+
325
+ global.__DEV__ = originalDev;
326
+ });
327
+ });
328
+ });
@@ -0,0 +1,5 @@
1
+ declare global {
2
+ const __DEV__: boolean;
3
+ }
4
+
5
+ export {};
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Domain Entity - Application Information
3
+ * Pure business logic, no external dependencies
4
+ * Part of About Domain
5
+ */
6
+ export interface AppInfo {
7
+ /** Application name */
8
+ name: string;
9
+ /** Application version (semver format) */
10
+ version: string;
11
+ /** Application description */
12
+ description?: string;
13
+ /** Developer/Company name */
14
+ developer?: string;
15
+ /** Contact email */
16
+ contactEmail?: string;
17
+ /** Website URL */
18
+ websiteUrl?: string;
19
+ /** Website display text */
20
+ websiteDisplay?: string;
21
+ /** More apps URL */
22
+ moreAppsUrl?: string;
23
+ }
24
+
25
+ /**
26
+ * Configuration interface for About component
27
+ * Fully configurable by parent application
28
+ */
29
+ export interface AboutConfig {
30
+ /** Application information */
31
+ appInfo?: Partial<AppInfo>;
32
+ /** Custom theme colors */
33
+ theme?: {
34
+ primary?: string;
35
+ secondary?: string;
36
+ background?: string;
37
+ text?: string;
38
+ border?: string;
39
+ };
40
+ /** Custom styling options */
41
+ style?: {
42
+ containerStyle?: Record<string, unknown>;
43
+ itemStyle?: Record<string, unknown>;
44
+ textStyle?: Record<string, unknown>;
45
+ iconStyle?: Record<string, unknown>;
46
+ };
47
+ /** Custom actions */
48
+ actions?: {
49
+ onWebsitePress?: () => void;
50
+ onEmailPress?: () => void;
51
+ onMoreAppsPress?: () => void;
52
+ };
53
+ /** Localized texts for section headers and labels */
54
+ texts?: {
55
+ contact?: string;
56
+ more?: string;
57
+ developer?: string;
58
+ email?: string;
59
+ website?: string;
60
+ moreApps?: string;
61
+ loading?: string;
62
+ errorPrefix?: string;
63
+ noInfo?: string;
64
+ versionPrefix?: string;
65
+ };
66
+ /** Navigation route name */
67
+ route?: string;
68
+ /** Default navigation route name */
69
+ defaultRoute?: string;
70
+ /** Section title */
71
+ title?: string;
72
+ /** Section description */
73
+ description?: string;
74
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Tests for AppInfo entity
3
+ */
4
+ import { AppInfo } from '../AppInfo';
5
+
6
+ describe('AppInfo Entity', () => {
7
+ const validAppInfo: AppInfo = {
8
+ name: 'Test App',
9
+ version: '1.0.0',
10
+ description: 'Test Description',
11
+ developer: 'Test Developer',
12
+ contactEmail: 'test@example.com',
13
+ websiteUrl: 'https://example.com',
14
+ websiteDisplay: 'example.com',
15
+ moreAppsUrl: 'https://apps.example.com',
16
+ };
17
+
18
+ describe('Validation', () => {
19
+ it('should accept valid AppInfo', () => {
20
+ expect(() => {
21
+ const appInfo: AppInfo = validAppInfo;
22
+ expect(appInfo.name).toBe('Test App');
23
+ expect(appInfo.version).toBe('1.0.0');
24
+ }).not.toThrow();
25
+ });
26
+
27
+ it('should accept minimal AppInfo', () => {
28
+ const minimalAppInfo: AppInfo = {
29
+ name: 'Minimal App',
30
+ version: '1.0.0',
31
+ };
32
+
33
+ expect(minimalAppInfo.name).toBe('Minimal App');
34
+ expect(minimalAppInfo.version).toBe('1.0.0');
35
+ expect(minimalAppInfo.description).toBeUndefined();
36
+ expect(minimalAppInfo.developer).toBeUndefined();
37
+ });
38
+
39
+ it('should accept AppInfo with optional fields', () => {
40
+ const appInfoWithOptionals: AppInfo = {
41
+ name: 'App',
42
+ version: '1.0.0',
43
+ description: 'Description',
44
+ developer: 'Developer',
45
+ };
46
+
47
+ expect(appInfoWithOptionals.description).toBe('Description');
48
+ expect(appInfoWithOptionals.developer).toBe('Developer');
49
+ expect(appInfoWithOptionals.contactEmail).toBeUndefined();
50
+ });
51
+ });
52
+
53
+ describe('Type Safety', () => {
54
+ it('should have correct types', () => {
55
+ const appInfo: AppInfo = validAppInfo;
56
+
57
+ expect(typeof appInfo.name).toBe('string');
58
+ expect(typeof appInfo.version).toBe('string');
59
+ expect(typeof appInfo.description).toBe('string');
60
+ expect(typeof appInfo.developer).toBe('string');
61
+ expect(typeof appInfo.contactEmail).toBe('string');
62
+ expect(typeof appInfo.websiteUrl).toBe('string');
63
+ expect(typeof appInfo.websiteDisplay).toBe('string');
64
+ expect(typeof appInfo.moreAppsUrl).toBe('string');
65
+ });
66
+
67
+ it('should allow undefined for optional fields', () => {
68
+ const appInfo: AppInfo = {
69
+ name: 'Test',
70
+ version: '1.0.0',
71
+ description: undefined,
72
+ developer: undefined,
73
+ contactEmail: undefined,
74
+ websiteUrl: undefined,
75
+ websiteDisplay: undefined,
76
+ moreAppsUrl: undefined,
77
+ };
78
+
79
+ expect(appInfo.description).toBeUndefined();
80
+ expect(appInfo.developer).toBeUndefined();
81
+ });
82
+ });
83
+
84
+ describe('Immutability', () => {
85
+ it('should be assignable but not inherently immutable', () => {
86
+ const appInfo: AppInfo = { ...validAppInfo };
87
+
88
+ // TypeScript allows mutation but we can test the behavior
89
+ appInfo.name = 'Modified App';
90
+ expect(appInfo.name).toBe('Modified App');
91
+ });
92
+ });
93
+ });
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Repository interface for About data
3
+ * Defines contract for data access layer
4
+ */
5
+ import { AppInfo } from '../entities/AppInfo';
6
+
7
+ export interface IAboutRepository {
8
+ /**
9
+ * Get application information
10
+ */
11
+ getAppInfo(): Promise<AppInfo>;
12
+
13
+ /**
14
+ * Save application information
15
+ */
16
+ saveAppInfo(appInfo: AppInfo): Promise<void>;
17
+
18
+ /**
19
+ * Update application information
20
+ */
21
+ updateAppInfo(updates: Partial<AppInfo>): Promise<AppInfo>;
22
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * About Domain
3
+ * User information, app details, version info
4
+ */
5
+
6
+ export * from './presentation/screens/AboutScreen';
7
+ export * from './presentation/components/AboutContent';
8
+ export * from './presentation/components/AboutSection';
9
+ export * from './presentation/components/AboutSettingItem';
10
+ export * from './presentation/components/AboutHeader';
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Repository implementation for About data
3
+ * Handles data persistence and retrieval
4
+ * Optimized for performance and memory safety
5
+ */
6
+ import { AppInfo } from '../../domain/entities/AppInfo';
7
+ import { IAboutRepository } from '../../domain/repositories/IAboutRepository';
8
+
9
+ export class AboutRepository implements IAboutRepository {
10
+ private appInfo: AppInfo | null = null;
11
+ private isDestroyed = false;
12
+
13
+ async getAppInfo(): Promise<AppInfo> {
14
+ if (this.isDestroyed) {
15
+ throw new Error('Repository has been destroyed');
16
+ }
17
+
18
+ if (!this.appInfo) {
19
+ throw new Error('App info not initialized');
20
+ }
21
+
22
+ // Return a deep copy to prevent mutations
23
+ return { ...this.appInfo };
24
+ }
25
+
26
+ async saveAppInfo(appInfo: AppInfo): Promise<void> {
27
+ if (this.isDestroyed) {
28
+ throw new Error('Repository has been destroyed');
29
+ }
30
+
31
+ // Validate input to prevent invalid data
32
+ if (!appInfo || typeof appInfo !== 'object') {
33
+ throw new Error('Invalid app info provided');
34
+ }
35
+
36
+ // Store a deep copy to prevent external mutations
37
+ this.appInfo = { ...appInfo };
38
+ }
39
+
40
+ async updateAppInfo(updates: Partial<AppInfo>): Promise<AppInfo> {
41
+ if (this.isDestroyed) {
42
+ throw new Error('Repository has been destroyed');
43
+ }
44
+
45
+ if (!this.appInfo) {
46
+ throw new Error('App info not initialized');
47
+ }
48
+
49
+ // Validate input to prevent invalid data
50
+ if (!updates || typeof updates !== 'object') {
51
+ throw new Error('Invalid updates provided');
52
+ }
53
+
54
+ // Create new object to prevent mutations
55
+ this.appInfo = { ...this.appInfo, ...updates };
56
+
57
+ // Return a deep copy to prevent mutations
58
+ return { ...this.appInfo };
59
+ }
60
+
61
+ /**
62
+ * Cleanup method to prevent memory leaks
63
+ */
64
+ destroy(): void {
65
+ this.appInfo = null;
66
+ this.isDestroyed = true;
67
+ }
68
+ }