@umituz/react-native-settings 4.17.27 → 4.17.31

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 (44) hide show
  1. package/package.json +15 -4
  2. package/src/domains/about/presentation/components/AboutSection.tsx +14 -71
  3. package/src/domains/appearance/application/ports/IAppearanceRepository.ts +8 -0
  4. package/src/domains/appearance/hooks/index.ts +1 -1
  5. package/src/domains/appearance/hooks/useAppearance.ts +18 -58
  6. package/src/domains/appearance/hooks/useAppearanceActions.ts +20 -128
  7. package/src/domains/appearance/infrastructure/repositories/AppearanceRepository.ts +34 -0
  8. package/src/domains/appearance/infrastructure/services/AppearanceService.ts +51 -0
  9. package/src/domains/appearance/presentation/components/AppearanceSection.tsx +2 -2
  10. package/src/domains/appearance/presentation/hooks/mutations/useAppearanceMutations.ts +36 -0
  11. package/src/domains/appearance/presentation/hooks/queries/useAppearanceQuery.ts +15 -0
  12. package/src/domains/disclaimer/presentation/components/DisclaimerModal.tsx +37 -40
  13. package/src/domains/faqs/presentation/components/FAQSection.tsx +1 -1
  14. package/src/domains/faqs/presentation/screens/FAQScreen.tsx +1 -1
  15. package/src/domains/feedback/presentation/components/FeedbackModal.tsx +11 -15
  16. package/src/domains/feedback/presentation/components/SupportSection.tsx +2 -2
  17. package/src/domains/legal/presentation/components/LegalItem.tsx +13 -129
  18. package/src/index.ts +19 -9
  19. package/src/infrastructure/repositories/SettingsRepository.ts +105 -0
  20. package/src/infrastructure/services/SettingsService.ts +47 -0
  21. package/src/presentation/components/SettingItem.tsx +77 -129
  22. package/src/presentation/components/SettingsFooter.tsx +9 -25
  23. package/src/presentation/components/SettingsSection.tsx +9 -20
  24. package/src/presentation/hooks/mutations/useSettingsMutations.ts +58 -0
  25. package/src/presentation/hooks/queries/useSettingsQuery.ts +27 -0
  26. package/src/presentation/hooks/useSettings.ts +45 -0
  27. package/src/presentation/screens/components/SettingsContent.tsx +20 -247
  28. package/src/presentation/screens/components/sections/CustomSettingsList.tsx +31 -0
  29. package/src/presentation/screens/components/sections/FeatureSettingsSection.tsx +68 -0
  30. package/src/presentation/screens/components/sections/IdentitySettingsSection.tsx +43 -0
  31. package/src/presentation/screens/components/sections/ProfileSectionLoader.tsx +47 -0
  32. package/src/presentation/screens/components/sections/SubscriptionSettingsItem.tsx +157 -0
  33. package/src/presentation/screens/components/sections/SupportSettingsSection.tsx +84 -0
  34. package/src/presentation/screens/hooks/useFeatureDetection.ts +1 -16
  35. package/src/presentation/screens/types/FeatureConfig.ts +35 -10
  36. package/src/presentation/screens/types/SettingsConfig.ts +7 -0
  37. package/src/presentation/screens/types/index.ts +1 -0
  38. package/src/domains/appearance/infrastructure/services/appearanceService.ts +0 -301
  39. package/src/domains/appearance/infrastructure/storage/appearanceStorage.ts +0 -120
  40. package/src/domains/appearance/infrastructure/stores/appearanceStore.ts +0 -132
  41. package/src/infrastructure/storage/SettingsStore.ts +0 -189
  42. package/src/infrastructure/storage/__tests__/SettingsStore.test.tsx +0 -302
  43. package/src/presentation/components/CloudSyncSetting.tsx +0 -58
  44. /package/src/{domain/repositories → application/ports}/ISettingsRepository.ts +0 -0
@@ -1,132 +0,0 @@
1
- /**
2
- * Appearance Store
3
- *
4
- * Zustand store for appearance state management
5
- * Single Responsibility: Pure state management only
6
- */
7
-
8
- import { create } from "zustand";
9
- import type { AppearanceSettings, AppearanceState } from "../../types";
10
-
11
- interface AppearanceStoreActions {
12
- // Pure state mutations only
13
- setSettings: (settings: AppearanceSettings) => void;
14
- setInitialized: (initialized: boolean) => void;
15
- updateThemeMode: (mode: AppearanceSettings["themeMode"]) => void;
16
- updateCustomColors: (colors: AppearanceSettings["customColors"]) => void;
17
- resetState: () => void;
18
- }
19
-
20
- type AppearanceStore = AppearanceState & AppearanceStoreActions;
21
-
22
- const DEFAULT_SETTINGS: AppearanceSettings = {
23
- themeMode: "dark", // Use dark mode as default
24
- };
25
-
26
- export const useAppearanceStore = create<AppearanceStore>((set, get) => ({
27
- settings: DEFAULT_SETTINGS,
28
- isInitialized: false,
29
-
30
- // Pure state mutations with performance optimizations
31
- setSettings: (settings: AppearanceSettings) => {
32
- // Prevent unnecessary updates if settings are the same
33
- const currentSettings = get().settings;
34
- if (JSON.stringify(currentSettings) === JSON.stringify(settings)) {
35
- if (__DEV__) {
36
- console.log("[AppearanceStore] Skipping settings update - no changes");
37
- }
38
- return;
39
- }
40
-
41
- if (__DEV__) {
42
- console.log("[AppearanceStore] Setting appearance settings:", settings);
43
- }
44
- set({ settings });
45
- },
46
-
47
- setInitialized: (initialized: boolean) => {
48
- // Prevent unnecessary updates if state is the same
49
- const currentInitialized = get().isInitialized;
50
- if (currentInitialized === initialized) {
51
- if (__DEV__) {
52
- console.log("[AppearanceStore] Skipping initialized update - no change");
53
- }
54
- return;
55
- }
56
-
57
- if (__DEV__) {
58
- console.log("[AppearanceStore] Setting initialized state:", initialized);
59
- }
60
- set({ isInitialized: initialized });
61
- },
62
-
63
- updateThemeMode: (mode: AppearanceSettings["themeMode"]) => {
64
- const currentSettings = get().settings;
65
-
66
- // Prevent unnecessary updates if mode is the same
67
- if (currentSettings.themeMode === mode) {
68
- if (__DEV__) {
69
- console.log("[AppearanceStore] Skipping theme mode update - no change");
70
- }
71
- return;
72
- }
73
-
74
- const newSettings: AppearanceSettings = {
75
- ...currentSettings,
76
- themeMode: mode,
77
- };
78
-
79
- if (__DEV__) {
80
- console.log("[AppearanceStore] Updating theme mode:", mode);
81
- }
82
-
83
- set({ settings: newSettings });
84
- },
85
-
86
- updateCustomColors: (colors: AppearanceSettings["customColors"]) => {
87
- const currentSettings = get().settings;
88
-
89
- // Prevent unnecessary updates if colors are the same
90
- if (JSON.stringify(currentSettings.customColors) === JSON.stringify(colors)) {
91
- if (__DEV__) {
92
- console.log("[AppearanceStore] Skipping custom colors update - no changes");
93
- }
94
- return;
95
- }
96
-
97
- const newSettings: AppearanceSettings = {
98
- ...currentSettings,
99
- customColors: colors,
100
- };
101
-
102
- if (__DEV__) {
103
- console.log("[AppearanceStore] Updating custom colors:", colors);
104
- }
105
-
106
- set({ settings: newSettings });
107
- },
108
-
109
- resetState: () => {
110
- const currentState = get();
111
-
112
- // Prevent unnecessary reset if already at default
113
- if (
114
- currentState.isInitialized === false &&
115
- JSON.stringify(currentState.settings) === JSON.stringify(DEFAULT_SETTINGS)
116
- ) {
117
- if (__DEV__) {
118
- console.log("[AppearanceStore] Skipping reset - already at default state");
119
- }
120
- return;
121
- }
122
-
123
- if (__DEV__) {
124
- console.log("[AppearanceStore] Resetting to default state");
125
- }
126
-
127
- set({
128
- settings: DEFAULT_SETTINGS,
129
- isInitialized: false,
130
- });
131
- },
132
- }));
@@ -1,189 +0,0 @@
1
- /**
2
- * Settings Store - Zustand State Management
3
- *
4
- * Global settings state for app preferences
5
- * Manages theme, language, notifications, and privacy settings
6
- *
7
- * DDD ARCHITECTURE: Uses @umituz/react-native-storage for all storage operations
8
- * - Type-safe storage with StorageKey enum
9
- * - Result pattern for error handling
10
- * - Single source of truth for all storage
11
- */
12
-
13
- import { create } from 'zustand';
14
- import { storageRepository, StorageKey, createUserKey, unwrap } from '@umituz/react-native-storage';
15
- import type { UserSettings } from '../../domain/repositories/ISettingsRepository';
16
-
17
- interface SettingsStore {
18
- // State
19
- settings: UserSettings | null;
20
- loading: boolean;
21
- error: string | null;
22
-
23
- // Actions
24
- loadSettings: (userId: string) => Promise<void>;
25
- updateSettings: (updates: Partial<UserSettings>) => Promise<void>;
26
- resetSettings: (userId: string) => Promise<void>;
27
- clearError: () => void;
28
- }
29
-
30
- const DEFAULT_OFFLINE_USER_ID = 'offline_user';
31
-
32
- const DEFAULT_SETTINGS_CACHE = new Map<string, UserSettings>();
33
-
34
- const getDefaultSettings = (userId: string): UserSettings => {
35
- if (DEFAULT_SETTINGS_CACHE.has(userId)) {
36
- return DEFAULT_SETTINGS_CACHE.get(userId)!;
37
- }
38
-
39
- const settings = {
40
- userId,
41
- theme: 'auto' as const,
42
- language: 'en-US',
43
- notificationsEnabled: true,
44
- emailNotifications: true,
45
- pushNotifications: true,
46
- soundEnabled: true,
47
- vibrationEnabled: true,
48
- privacyMode: false,
49
- disclaimerAccepted: false,
50
- updatedAt: new Date(),
51
- };
52
-
53
- DEFAULT_SETTINGS_CACHE.set(userId, settings);
54
- return settings;
55
- };
56
-
57
- export const useSettingsStore = create<SettingsStore>((set, get) => ({
58
- settings: null,
59
- loading: false,
60
- error: null,
61
-
62
- loadSettings: async (userId: string) => {
63
- if (__DEV__) {
64
- console.log('SettingsStore: Loading settings for user:', userId);
65
- }
66
-
67
- set({ loading: true, error: null });
68
-
69
- try {
70
- const defaultSettings = getDefaultSettings(userId);
71
- const storageKey = createUserKey(StorageKey.USER_PREFERENCES, userId);
72
-
73
- // ✅ DRY: Storage domain handles JSON parse, error handling
74
- const result = await storageRepository.getItem<UserSettings>(storageKey, defaultSettings);
75
- const data = unwrap(result, defaultSettings);
76
-
77
- // ✅ CLEAN CODE: Auto-save defaults if not exists
78
- if (!result.success) {
79
- await storageRepository.setItem(storageKey, defaultSettings);
80
- }
81
-
82
- set({
83
- settings: data,
84
- loading: false,
85
- error: null,
86
- });
87
-
88
- if (__DEV__) {
89
- console.log('SettingsStore: Settings loaded successfully');
90
- }
91
- } catch (error) {
92
- if (__DEV__) {
93
- console.error('SettingsStore: Failed to load settings:', error);
94
- }
95
- set({ loading: false, error: 'Failed to load settings' });
96
- }
97
- },
98
-
99
- updateSettings: async (updates: Partial<UserSettings>) => {
100
- if (__DEV__) {
101
- console.log('SettingsStore: Updating settings with:', updates);
102
- }
103
-
104
- const { settings } = get();
105
-
106
- // ✅ CLEAN CODE: Auto-initialize if settings not loaded
107
- if (!settings) {
108
- await get().loadSettings(DEFAULT_OFFLINE_USER_ID);
109
- }
110
-
111
- // ✅ DEFENSIVE: Verify settings loaded successfully
112
- const currentSettings = get().settings;
113
- if (!currentSettings) {
114
- const errorMsg = 'Failed to initialize settings';
115
- if (__DEV__) {
116
- console.error('SettingsStore:', errorMsg);
117
- }
118
- set({ error: errorMsg });
119
- return;
120
- }
121
-
122
- set({ loading: true, error: null });
123
-
124
- try {
125
- const updatedSettings: UserSettings = {
126
- ...currentSettings,
127
- ...updates,
128
- updatedAt: new Date(),
129
- };
130
-
131
- const storageKey = createUserKey(StorageKey.USER_PREFERENCES, currentSettings.userId);
132
-
133
- // ✅ DRY: Storage domain replaces JSON.stringify + AsyncStorage + try/catch
134
- const result = await storageRepository.setItem(storageKey, updatedSettings);
135
-
136
- set({
137
- settings: result.success ? updatedSettings : currentSettings,
138
- loading: false,
139
- error: null,
140
- });
141
-
142
- if (__DEV__) {
143
- console.log('SettingsStore: Settings updated successfully');
144
- }
145
- } catch (error) {
146
- if (__DEV__) {
147
- console.error('SettingsStore: Failed to update settings:', error);
148
- }
149
- set({ loading: false, error: 'Failed to update settings' });
150
- }
151
- },
152
-
153
- resetSettings: async (userId: string) => {
154
- set({ loading: true, error: null });
155
-
156
- const defaultSettings = getDefaultSettings(userId);
157
- const storageKey = createUserKey(StorageKey.USER_PREFERENCES, userId);
158
-
159
- // ✅ DRY: Storage domain replaces JSON.stringify + AsyncStorage + try/catch
160
- const result = await storageRepository.setItem(storageKey, defaultSettings);
161
-
162
- set({
163
- settings: result.success ? defaultSettings : get().settings,
164
- loading: false,
165
- error: null,
166
- });
167
- },
168
-
169
- clearError: () => set({ error: null }),
170
- }));
171
-
172
- /**
173
- * Hook for accessing settings state
174
- */
175
- export const useSettings = () => {
176
- const { settings, loading, error, loadSettings, updateSettings, resetSettings, clearError } =
177
- useSettingsStore();
178
-
179
- return {
180
- settings,
181
- loading,
182
- error,
183
- loadSettings,
184
- updateSettings,
185
- resetSettings,
186
- clearError,
187
- };
188
- };
189
-
@@ -1,302 +0,0 @@
1
- /**
2
- * Tests for SettingsStore and useSettings Hook
3
- */
4
-
5
- import { renderHook, act } from '@testing-library/react-hooks';
6
- import { useSettings, useSettingsStore } from '../SettingsStore';
7
- import type { UserSettings } from '../../../domain/repositories/ISettingsRepository';
8
-
9
- // Mock storage repository
10
- const mockStorageRepository = {
11
- getItem: jest.fn(),
12
- setItem: jest.fn(),
13
- removeItem: jest.fn(),
14
- clear: jest.fn(),
15
- };
16
-
17
- jest.mock('@umituz/react-native-storage', () => ({
18
- storageRepository: mockStorageRepository,
19
- StorageKey: {
20
- SETTINGS: 'settings',
21
- },
22
- createUserKey: (key: string, userId: string) => `${key}_${userId}`,
23
- unwrap: (result: any, defaultValue: any) => result.success ? result.data : defaultValue,
24
- }));
25
-
26
- describe('SettingsStore', () => {
27
- const mockUserId = 'test-user-123';
28
- const mockSettings: UserSettings = {
29
- userId: mockUserId,
30
- theme: 'dark',
31
- language: 'en-US',
32
- notificationsEnabled: true,
33
- emailNotifications: false,
34
- pushNotifications: true,
35
- soundEnabled: true,
36
- vibrationEnabled: false,
37
- privacyMode: false,
38
- updatedAt: new Date('2023-01-01'),
39
- };
40
-
41
- beforeEach(() => {
42
- jest.clearAllMocks();
43
- });
44
-
45
- describe('loadSettings', () => {
46
- it('loads settings successfully from storage', async () => {
47
- mockStorageRepository.getItem.mockResolvedValue({
48
- success: true,
49
- data: mockSettings,
50
- });
51
-
52
- const { result } = renderHook(() => useSettingsStore());
53
-
54
- await act(async () => {
55
- await result.current.loadSettings(mockUserId);
56
- });
57
-
58
- expect(result.current.settings).toEqual(mockSettings);
59
- expect(result.current.loading).toBe(false);
60
- expect(result.current.error).toBeNull();
61
- expect(mockStorageRepository.getItem).toHaveBeenCalledWith(
62
- `settings_${mockUserId}`,
63
- expect.any(Object)
64
- );
65
- });
66
-
67
- it('uses default settings when storage fails', async () => {
68
- mockStorageRepository.getItem.mockResolvedValue({
69
- success: false,
70
- error: 'Storage error',
71
- });
72
- mockStorageRepository.setItem.mockResolvedValue({
73
- success: true,
74
- });
75
-
76
- const { result } = renderHook(() => useSettingsStore());
77
-
78
- await act(async () => {
79
- await result.current.loadSettings(mockUserId);
80
- });
81
-
82
- expect(result.current.settings).toBeDefined();
83
- expect(result.current.settings?.userId).toBe(mockUserId);
84
- expect(result.current.settings?.theme).toBe('auto');
85
- expect(result.current.loading).toBe(false);
86
- expect(result.current.error).toBeNull();
87
- expect(mockStorageRepository.setItem).toHaveBeenCalled();
88
- });
89
-
90
- it('handles loading state correctly', async () => {
91
- mockStorageRepository.getItem.mockImplementation(() => new Promise(resolve =>
92
- setTimeout(() => resolve({ success: true, data: mockSettings }), 100)
93
- ));
94
-
95
- const { result } = renderHook(() => useSettingsStore());
96
-
97
- act(() => {
98
- result.current.loadSettings(mockUserId);
99
- });
100
-
101
- expect(result.current.loading).toBe(true);
102
-
103
- await act(async () => {
104
- await new Promise(resolve => setTimeout(resolve, 150));
105
- });
106
-
107
- expect(result.current.loading).toBe(false);
108
- expect(result.current.settings).toEqual(mockSettings);
109
- });
110
-
111
- it('handles storage exceptions', async () => {
112
- mockStorageRepository.getItem.mockRejectedValue(new Error('Storage exception'));
113
-
114
- const { result } = renderHook(() => useSettingsStore());
115
-
116
- await act(async () => {
117
- await result.current.loadSettings(mockUserId);
118
- });
119
-
120
- expect(result.current.loading).toBe(false);
121
- expect(result.current.error).toBe('Failed to load settings');
122
- expect(result.current.settings).toBeNull();
123
- });
124
- });
125
-
126
- describe('updateSettings', () => {
127
- it('updates settings successfully', async () => {
128
- // First load settings
129
- mockStorageRepository.getItem.mockResolvedValue({
130
- success: true,
131
- data: mockSettings,
132
- });
133
- mockStorageRepository.setItem.mockResolvedValue({
134
- success: true,
135
- });
136
-
137
- const { result } = renderHook(() => useSettingsStore());
138
-
139
- await act(async () => {
140
- await result.current.loadSettings(mockUserId);
141
- });
142
-
143
- // Then update settings
144
- const updates = { theme: 'light' as const, notificationsEnabled: false };
145
-
146
- await act(async () => {
147
- await result.current.updateSettings(updates);
148
- });
149
-
150
- expect(result.current.settings?.theme).toBe('light');
151
- expect(result.current.settings?.notificationsEnabled).toBe(false);
152
- expect(result.current.settings?.updatedAt).toBeInstanceOf(Date);
153
- expect(mockStorageRepository.setItem).toHaveBeenCalledWith(
154
- `settings_${mockUserId}`,
155
- expect.objectContaining({
156
- theme: 'light',
157
- notificationsEnabled: false,
158
- })
159
- );
160
- });
161
-
162
- it('auto-initializes settings when not loaded', async () => {
163
- mockStorageRepository.getItem
164
- .mockResolvedValueOnce({ success: false, error: 'Not found' })
165
- .mockResolvedValueOnce({ success: true, data: expect.any(Object) });
166
- mockStorageRepository.setItem.mockResolvedValue({ success: true });
167
-
168
- const { result } = renderHook(() => useSettingsStore());
169
-
170
- await act(async () => {
171
- await result.current.updateSettings({ theme: 'light' as const });
172
- });
173
-
174
- expect(result.current.settings).toBeDefined();
175
- expect(result.current.settings?.theme).toBe('light');
176
- });
177
-
178
- it('handles update failures gracefully', async () => {
179
- mockStorageRepository.getItem.mockResolvedValue({
180
- success: true,
181
- data: mockSettings,
182
- });
183
- mockStorageRepository.setItem.mockResolvedValue({
184
- success: false,
185
- error: 'Update failed',
186
- });
187
-
188
- const { result } = renderHook(() => useSettingsStore());
189
-
190
- await act(async () => {
191
- await result.current.loadSettings(mockUserId);
192
- });
193
-
194
- const originalSettings = result.current.settings;
195
-
196
- await act(async () => {
197
- await result.current.updateSettings({ theme: 'light' as const });
198
- });
199
-
200
- // Settings should remain unchanged on failure
201
- expect(result.current.settings).toEqual(originalSettings);
202
- expect(result.current.loading).toBe(false);
203
- });
204
-
205
- it('handles update exceptions', async () => {
206
- mockStorageRepository.getItem.mockResolvedValue({
207
- success: true,
208
- data: mockSettings,
209
- });
210
- mockStorageRepository.setItem.mockRejectedValue(new Error('Update exception'));
211
-
212
- const { result } = renderHook(() => useSettingsStore());
213
-
214
- await act(async () => {
215
- await result.current.loadSettings(mockUserId);
216
- });
217
-
218
- await act(async () => {
219
- await result.current.updateSettings({ theme: 'light' as const });
220
- });
221
-
222
- expect(result.current.loading).toBe(false);
223
- expect(result.current.error).toBe('Failed to update settings');
224
- });
225
- });
226
-
227
- describe('resetSettings', () => {
228
- it('resets settings to defaults', async () => {
229
- mockStorageRepository.getItem.mockResolvedValue({
230
- success: true,
231
- data: mockSettings,
232
- });
233
- mockStorageRepository.setItem.mockResolvedValue({
234
- success: true,
235
- });
236
-
237
- const { result } = renderHook(() => useSettingsStore());
238
-
239
- await act(async () => {
240
- await result.current.loadSettings(mockUserId);
241
- });
242
-
243
- await act(async () => {
244
- await result.current.resetSettings(mockUserId);
245
- });
246
-
247
- expect(result.current.settings?.theme).toBe('auto');
248
- expect(result.current.settings?.language).toBe('en-US');
249
- expect(result.current.settings?.notificationsEnabled).toBe(true);
250
- expect(mockStorageRepository.setItem).toHaveBeenCalledWith(
251
- `settings_${mockUserId}`,
252
- expect.objectContaining({
253
- theme: 'auto',
254
- language: 'en-US',
255
- })
256
- );
257
- });
258
- });
259
-
260
- describe('clearError', () => {
261
- it('clears error state', async () => {
262
- mockStorageRepository.getItem.mockRejectedValue(new Error('Test error'));
263
-
264
- const { result } = renderHook(() => useSettingsStore());
265
-
266
- await act(async () => {
267
- await result.current.loadSettings(mockUserId);
268
- });
269
-
270
- expect(result.current.error).toBe('Failed to load settings');
271
-
272
- act(() => {
273
- result.current.clearError();
274
- });
275
-
276
- expect(result.current.error).toBeNull();
277
- });
278
- });
279
- });
280
-
281
- describe('useSettings Hook', () => {
282
- it('provides all store methods and state', () => {
283
- const { result } = renderHook(() => useSettings());
284
-
285
- expect(result.current).toHaveProperty('settings');
286
- expect(result.current).toHaveProperty('loading');
287
- expect(result.current).toHaveProperty('error');
288
- expect(result.current).toHaveProperty('loadSettings');
289
- expect(result.current).toHaveProperty('updateSettings');
290
- expect(result.current).toHaveProperty('resetSettings');
291
- expect(result.current).toHaveProperty('clearError');
292
- });
293
-
294
- it('is a thin wrapper around useSettingsStore', () => {
295
- const storeResult = renderHook(() => useSettingsStore()).result;
296
- const hookResult = renderHook(() => useSettings()).result;
297
-
298
- expect(Object.keys(storeResult.current)).toEqual(
299
- expect.arrayContaining(Object.keys(hookResult.current))
300
- );
301
- });
302
- });
@@ -1,58 +0,0 @@
1
- /**
2
- * Cloud Sync Setting Component
3
- * Single Responsibility: Display cloud sync setting item
4
- */
5
-
6
- import React, { useCallback } from "react";
7
- import { SettingItem } from "./SettingItem";
8
- import type { SettingItemProps } from "./SettingItem";
9
-
10
- export interface CloudSyncSettingProps {
11
- title?: string;
12
- description?: string;
13
- isSyncing?: boolean;
14
- lastSynced?: Date | null;
15
- onPress?: () => void;
16
- iconColor?: string;
17
- titleColor?: string;
18
- }
19
-
20
- export const CloudSyncSetting: React.FC<CloudSyncSettingProps> = ({
21
- title,
22
- description,
23
- isSyncing = false,
24
- lastSynced,
25
- onPress,
26
- iconColor,
27
- titleColor,
28
- }) => {
29
- const formatLastSynced = useCallback((date: Date | null | undefined): string => {
30
- if (!date) return "never_synced";
31
- const now = new Date();
32
- const diff = now.getTime() - date.getTime();
33
- const minutes = Math.floor(diff / 60000);
34
- const hours = Math.floor(minutes / 60);
35
- const days = Math.floor(hours / 24);
36
-
37
- if (minutes < 1) return "just_now";
38
- if (minutes < 60) return `${minutes}m_ago`;
39
- if (hours < 24) return `${hours}h_ago`;
40
- if (days < 7) return `${days}d_ago`;
41
- return date.toLocaleDateString();
42
- }, []);
43
-
44
- const displayDescription = description || (isSyncing ? "syncing" : lastSynced ? `last_synced_${formatLastSynced(lastSynced)}` : "sync_to_cloud");
45
-
46
- return (
47
- <SettingItem
48
- icon="cloud-outline"
49
- title={title || "cloud_sync"}
50
- value={displayDescription}
51
- onPress={onPress}
52
- iconColor={iconColor}
53
- titleColor={titleColor}
54
- disabled={isSyncing}
55
- />
56
- );
57
- };
58
-