@umituz/react-native-settings 5.2.19 → 5.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/domains/about/presentation/hooks/useAboutInfo.ts +63 -98
- package/src/domains/feedback/presentation/components/SupportSection.tsx +2 -1
- package/src/domains/localization/infrastructure/hooks/useTranslation.ts +0 -3
- package/src/domains/localization/infrastructure/storage/LanguageInitializer.ts +0 -4
- package/src/domains/localization/infrastructure/storage/localizationStoreUtils.ts +6 -7
- package/src/domains/localization/presentation/components/LanguageItem.tsx +0 -2
- package/src/domains/localization/presentation/providers/LocalizationManager.tsx +2 -1
- package/src/domains/notifications/index.ts +7 -6
- package/src/domains/notifications/infrastructure/services/NotificationBadgeManager.ts +1 -1
- package/src/domains/notifications/infrastructure/services/NotificationManager.ts +1 -1
- package/src/domains/notifications/infrastructure/services/NotificationPermissions.ts +1 -1
- package/src/domains/notifications/infrastructure/storage/UnifiedNotificationStore.ts +223 -0
- package/src/domains/notifications/presentation/hooks/useNotificationSettingsUI.ts +2 -2
- package/src/domains/notifications/presentation/screens/NotificationSettingsScreen.tsx +1 -1
- package/src/domains/notifications/quietHours/infrastructure/hooks/useQuietHoursActions.ts +6 -6
- package/src/domains/notifications/reminders/infrastructure/hooks/useReminderActions.ts +5 -5
- package/src/domains/notifications/reminders/presentation/screens/ReminderListScreen.tsx +1 -1
- package/src/domains/rating/presentation/hooks/useAppRating.tsx +2 -1
- package/src/infrastructure/storage/storeConfig.ts +114 -0
- package/src/infrastructure/utils/async/core.ts +3 -2
- package/src/infrastructure/utils/errorHandlers.ts +4 -2
- package/src/infrastructure/utils/index.ts +1 -1
- package/src/presentation/components/SettingsNavigationItem.tsx +109 -0
- package/src/presentation/navigation/utils/index.ts +8 -1
- package/src/presentation/navigation/utils/navigationHelpers.ts +118 -0
- package/src/presentation/screens/components/SettingsContent.tsx +21 -11
- package/src/presentation/screens/components/sections/SupportSettingsSection.tsx +4 -2
- package/src/presentation/screens/types/UserFeatureConfig.ts +5 -4
- package/src/presentation/utils/config-creators/feature-configs.ts +3 -2
- package/src/presentation/utils/settingsConfigFactory.ts +2 -1
- package/src/utils/appUtils.ts +0 -6
- package/src/utils/errorUtils.ts +54 -0
- package/src/utils/hooks/index.ts +6 -0
- package/src/utils/hooks/useAsyncStateUpdate.ts +114 -0
- package/src/utils/hooks/useMountSafety.ts +30 -0
- package/src/utils/index.ts +2 -0
- package/src/domains/about/presentation/hooks/useAboutInfo.utils.ts +0 -167
- package/src/domains/notifications/infrastructure/storage/NotificationsStore.ts +0 -45
- package/src/domains/notifications/infrastructure/utils/dev.ts +0 -22
- package/src/domains/notifications/reminders/infrastructure/storage/RemindersStore.ts +0 -152
- package/src/infrastructure/utils/styleUtils.ts +0 -7
- package/src/presentation/screens/components/GamificationSettingsItem.tsx +0 -55
- package/src/presentation/screens/components/SubscriptionSettingsItem.tsx +0 -38
- package/src/presentation/screens/components/VideoTutorialSettingsItem.tsx +0 -51
- package/src/presentation/screens/components/WalletSettingsItem.tsx +0 -36
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.21",
|
|
4
4
|
"description": "Complete settings hub for React Native apps - consolidated package with settings, localization, about, legal, appearance, feedback, FAQs, rating, and gamification",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -1,135 +1,103 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hook for managing About information
|
|
3
3
|
* Provides reactive state management for About data
|
|
4
|
-
*
|
|
4
|
+
* Refactored to use useAsyncStateUpdate for clean async state management
|
|
5
5
|
*/
|
|
6
6
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
7
7
|
import type { AppInfo, AboutConfig } from '../../domain/entities/AppInfo';
|
|
8
8
|
import { AboutRepository } from '../../infrastructure/repositories/AboutRepository';
|
|
9
|
+
import { useAsyncStateUpdate } from '../../../../utils/hooks/useAsyncStateUpdate';
|
|
10
|
+
import { createDefaultAppInfo } from '../../utils/AppInfoFactory';
|
|
9
11
|
import type { UseAboutInfoOptions, UseAboutInfoReturn } from './useAboutInfo.types';
|
|
10
|
-
import {
|
|
11
|
-
setErrorIfMounted,
|
|
12
|
-
initializeAppInfo,
|
|
13
|
-
updateAppInfoConfig,
|
|
14
|
-
updateAppInfoPartial,
|
|
15
|
-
refreshAppInfo,
|
|
16
|
-
} from './useAboutInfo.utils';
|
|
17
12
|
|
|
18
13
|
export const useAboutInfo = (
|
|
19
14
|
options: UseAboutInfoOptions = {}
|
|
20
15
|
): UseAboutInfoReturn => {
|
|
21
16
|
const { initialConfig, autoInit } = options;
|
|
22
17
|
const [repository] = useState(() => new AboutRepository());
|
|
23
|
-
const [appInfo, setAppInfo] = useState<AppInfo | null>(null);
|
|
24
|
-
const [loading, setLoading] = useState(false);
|
|
25
|
-
const [error, setError] = useState<string | null>(null);
|
|
26
|
-
|
|
27
18
|
const isInitializedRef = useRef(false);
|
|
28
|
-
const isMountedRef = useRef(true);
|
|
29
19
|
|
|
20
|
+
// Use the new useAsyncStateUpdate hook for clean async state management
|
|
21
|
+
const { data: appInfo, loading, error, execute, setData: setAppInfo, setError } = useAsyncStateUpdate<AppInfo>({
|
|
22
|
+
initialData: null,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Initialize app info with config
|
|
27
|
+
*/
|
|
30
28
|
const initialize = useCallback(
|
|
31
|
-
(config: AboutConfig) =>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
async (config: AboutConfig) => {
|
|
30
|
+
if (isInitializedRef.current) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await execute(async () => {
|
|
35
|
+
const defaultAppInfo = createDefaultAppInfo(config);
|
|
36
|
+
await repository.saveAppInfo(defaultAppInfo);
|
|
37
|
+
isInitializedRef.current = true;
|
|
38
|
+
return defaultAppInfo;
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
[repository, execute]
|
|
41
42
|
);
|
|
42
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Update app info with new config
|
|
46
|
+
*/
|
|
43
47
|
const update = useCallback(
|
|
44
|
-
(config: AboutConfig) =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
[repository]
|
|
48
|
+
async (config: AboutConfig) => {
|
|
49
|
+
await execute(async () => {
|
|
50
|
+
const updatedAppInfo = createDefaultAppInfo(config);
|
|
51
|
+
await repository.saveAppInfo(updatedAppInfo);
|
|
52
|
+
return updatedAppInfo;
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
[repository, execute]
|
|
53
56
|
);
|
|
54
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Update app info with partial updates
|
|
60
|
+
*/
|
|
55
61
|
const updateAppInfoCallback = useCallback(
|
|
56
|
-
(updates: Partial<AppInfo>) => {
|
|
62
|
+
async (updates: Partial<AppInfo>) => {
|
|
57
63
|
if (!appInfo) {
|
|
58
|
-
|
|
59
|
-
return
|
|
64
|
+
setError('App info not initialized');
|
|
65
|
+
return;
|
|
60
66
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
repository
|
|
64
|
-
|
|
65
|
-
setAppInfo,
|
|
66
|
-
setError,
|
|
67
|
-
setLoading
|
|
68
|
-
);
|
|
67
|
+
|
|
68
|
+
await execute(async () => {
|
|
69
|
+
return await repository.updateAppInfo(updates);
|
|
70
|
+
});
|
|
69
71
|
},
|
|
70
|
-
[repository, appInfo]
|
|
72
|
+
[repository, appInfo, execute, setError]
|
|
71
73
|
);
|
|
72
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Refresh app info from repository
|
|
77
|
+
*/
|
|
73
78
|
const refresh = useCallback(
|
|
74
|
-
() =>
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
),
|
|
81
|
-
[repository]
|
|
79
|
+
async () => {
|
|
80
|
+
await execute(async () => {
|
|
81
|
+
return await repository.getAppInfo();
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
[repository, execute]
|
|
82
85
|
);
|
|
83
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Reset to initial state
|
|
89
|
+
*/
|
|
84
90
|
const reset = useCallback(() => {
|
|
85
|
-
if (!isMountedRef.current) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
91
|
setAppInfo(null);
|
|
90
92
|
setError(null);
|
|
91
|
-
setLoading(false);
|
|
92
93
|
isInitializedRef.current = false;
|
|
93
|
-
}, []);
|
|
94
|
-
|
|
95
|
-
// Cleanup on unmount
|
|
96
|
-
useEffect(() => {
|
|
97
|
-
return () => {
|
|
98
|
-
isMountedRef.current = false;
|
|
99
|
-
|
|
100
|
-
if (repository && typeof repository.destroy === 'function') {
|
|
101
|
-
try {
|
|
102
|
-
repository.destroy();
|
|
103
|
-
} catch (error) {
|
|
104
|
-
// Log cleanup error but don't throw
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
}, [repository]);
|
|
94
|
+
}, [setAppInfo, setError]);
|
|
109
95
|
|
|
110
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Auto-initialize if config provided
|
|
98
|
+
*/
|
|
111
99
|
useEffect(() => {
|
|
112
|
-
if (
|
|
113
|
-
initialConfig &&
|
|
114
|
-
autoInit !== false &&
|
|
115
|
-
isMountedRef.current &&
|
|
116
|
-
!isInitializedRef.current
|
|
117
|
-
) {
|
|
118
|
-
// Dynamic import to avoid require issues
|
|
119
|
-
import('../../utils/AppInfoFactory').then(({ createDefaultAppInfo }) => {
|
|
120
|
-
if (isMountedRef.current) {
|
|
121
|
-
const defaultAppInfo = createDefaultAppInfo(initialConfig);
|
|
122
|
-
setAppInfo(defaultAppInfo);
|
|
123
|
-
isInitializedRef.current = true;
|
|
124
|
-
}
|
|
125
|
-
}).catch((_error) => {
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}, [initialConfig, autoInit]);
|
|
129
|
-
|
|
130
|
-
// Auto-initialize if autoInit is true
|
|
131
|
-
useEffect(() => {
|
|
132
|
-
if (autoInit === true && initialConfig && isMountedRef.current) {
|
|
100
|
+
if (autoInit && initialConfig && !isInitializedRef.current) {
|
|
133
101
|
initialize(initialConfig);
|
|
134
102
|
}
|
|
135
103
|
}, [autoInit, initialConfig, initialize]);
|
|
@@ -145,6 +113,3 @@ export const useAboutInfo = (
|
|
|
145
113
|
reset,
|
|
146
114
|
};
|
|
147
115
|
};
|
|
148
|
-
|
|
149
|
-
// Re-export types for convenience
|
|
150
|
-
export type { UseAboutInfoOptions, UseAboutInfoReturn } from './useAboutInfo.types';
|
|
@@ -8,6 +8,7 @@ import React, { useState, useCallback } from "react";
|
|
|
8
8
|
import { Linking } from "react-native";
|
|
9
9
|
import { FeedbackModal } from "./FeedbackModal";
|
|
10
10
|
import type { FeedbackType } from "../../domain/entities/FeedbackEntity";
|
|
11
|
+
import { isDev } from "../../../../utils/devUtils";
|
|
11
12
|
|
|
12
13
|
export interface FeedbackConfig {
|
|
13
14
|
enabled?: boolean;
|
|
@@ -75,7 +76,7 @@ export const SupportSection: React.FC<SupportSectionProps> = ({
|
|
|
75
76
|
setModalVisible(false);
|
|
76
77
|
} catch (error) {
|
|
77
78
|
// If the passed onSubmit throws, we log it.
|
|
78
|
-
if (
|
|
79
|
+
if (isDev()) {
|
|
79
80
|
console.error('[SupportSection] Failed to submit feedback:', error);
|
|
80
81
|
}
|
|
81
82
|
// Optionally keep modal open? Or close it?
|
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
* - i18n setup
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
declare const __DEV__: boolean;
|
|
11
|
-
|
|
12
10
|
import { storageRepository } from '@umituz/react-native-design-system';
|
|
13
11
|
import i18n from '../config/i18n';
|
|
14
12
|
import { languageRepository } from '../repository/LanguageRepository';
|
|
@@ -33,8 +31,6 @@ export class LanguageInitializer {
|
|
|
33
31
|
|
|
34
32
|
return finalLanguage;
|
|
35
33
|
} catch {
|
|
36
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
37
|
-
}
|
|
38
34
|
return await this.setupFallbackLanguage();
|
|
39
35
|
}
|
|
40
36
|
}
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
import { LanguageInitializer } from "./LanguageInitializer";
|
|
7
7
|
import { LanguageSwitcher } from "./LanguageSwitcher";
|
|
8
8
|
import { languageRepository } from "../repository/LanguageRepository";
|
|
9
|
-
|
|
10
|
-
declare const __DEV__: boolean;
|
|
9
|
+
import { isDev } from "../../../../utils/devUtils";
|
|
11
10
|
|
|
12
11
|
export const LANGUAGE_SWITCH_DEBOUNCE_MS = 300;
|
|
13
12
|
|
|
@@ -45,7 +44,7 @@ export class InitializationManager {
|
|
|
45
44
|
});
|
|
46
45
|
} catch (error) {
|
|
47
46
|
// Log and set fallback state
|
|
48
|
-
if (
|
|
47
|
+
if (isDev()) {
|
|
49
48
|
console.error("Localization initialization failed:", error);
|
|
50
49
|
}
|
|
51
50
|
|
|
@@ -103,14 +102,14 @@ export class LanguageSwitchManager {
|
|
|
103
102
|
this.pendingResolvers.push(pendingItem);
|
|
104
103
|
|
|
105
104
|
this.timer = setTimeout(async () => {
|
|
106
|
-
if (
|
|
105
|
+
if (isDev()) {
|
|
107
106
|
console.log("[Localization] Switching language to:", languageCode);
|
|
108
107
|
}
|
|
109
108
|
|
|
110
109
|
try {
|
|
111
110
|
const result = await LanguageSwitcher.switchLanguage(languageCode);
|
|
112
111
|
|
|
113
|
-
if (
|
|
112
|
+
if (isDev()) {
|
|
114
113
|
console.log("[Localization] Language switched successfully");
|
|
115
114
|
}
|
|
116
115
|
|
|
@@ -121,7 +120,7 @@ export class LanguageSwitchManager {
|
|
|
121
120
|
|
|
122
121
|
setState(stateUpdate);
|
|
123
122
|
|
|
124
|
-
if (
|
|
123
|
+
if (isDev()) {
|
|
125
124
|
console.log("[Localization] State updated:", stateUpdate);
|
|
126
125
|
}
|
|
127
126
|
|
|
@@ -133,7 +132,7 @@ export class LanguageSwitchManager {
|
|
|
133
132
|
const errorMessage =
|
|
134
133
|
error instanceof Error ? error : new Error(String(error));
|
|
135
134
|
|
|
136
|
-
if (
|
|
135
|
+
if (isDev()) {
|
|
137
136
|
console.error("[Localization] Language switch failed:", errorMessage);
|
|
138
137
|
}
|
|
139
138
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import React, { useEffect, useState } from "react";
|
|
7
7
|
import { I18nInitializer } from "../../infrastructure/config/I18nInitializer";
|
|
8
8
|
import { useLocalizationStore } from "../../infrastructure/storage/LocalizationStore";
|
|
9
|
+
import { isDev } from "../../../../utils/devUtils";
|
|
9
10
|
|
|
10
11
|
interface LocalizationProviderProps {
|
|
11
12
|
children: React.ReactNode;
|
|
@@ -32,7 +33,7 @@ export const LocalizationManager: React.FC<LocalizationProviderProps> = ({
|
|
|
32
33
|
setIsI18nReady(true);
|
|
33
34
|
}
|
|
34
35
|
} catch (error) {
|
|
35
|
-
if (
|
|
36
|
+
if (isDev()) {
|
|
36
37
|
console.error('[LocalizationManager] Initialization failed:', error);
|
|
37
38
|
}
|
|
38
39
|
if (isMounted) {
|
|
@@ -55,18 +55,19 @@ export { NotificationManager } from './infrastructure/services/NotificationManag
|
|
|
55
55
|
// STORES
|
|
56
56
|
// ============================================================================
|
|
57
57
|
|
|
58
|
-
export { useNotificationsStore, useNotifications } from './infrastructure/storage/NotificationsStore';
|
|
59
58
|
export {
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
useNotificationStore,
|
|
60
|
+
useNotificationPermissions,
|
|
61
|
+
useNotificationInitialized,
|
|
62
|
+
useNotificationPreferences,
|
|
63
|
+
useQuietHours,
|
|
62
64
|
useReminders,
|
|
63
65
|
useEnabledReminders,
|
|
64
66
|
useReminderById,
|
|
65
|
-
useNotificationPreferences,
|
|
66
|
-
useQuietHours,
|
|
67
67
|
useRemindersLoading,
|
|
68
68
|
useRemindersInitialized,
|
|
69
|
-
|
|
69
|
+
useNotifications, // Legacy compatibility
|
|
70
|
+
} from './infrastructure/storage/UnifiedNotificationStore';
|
|
70
71
|
|
|
71
72
|
// ============================================================================
|
|
72
73
|
// HOOKS
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Notifications from 'expo-notifications';
|
|
2
2
|
import { Platform } from 'react-native';
|
|
3
|
-
import { devError } from '
|
|
3
|
+
import { devError } from '../../../../utils/devUtils';
|
|
4
4
|
|
|
5
5
|
export class NotificationBadgeManager {
|
|
6
6
|
async getBadgeCount(): Promise<number> {
|
|
@@ -9,7 +9,7 @@ import * as Notifications from 'expo-notifications';
|
|
|
9
9
|
import { NotificationPermissions } from './NotificationPermissions';
|
|
10
10
|
import { NotificationScheduler } from './NotificationScheduler';
|
|
11
11
|
import { NotificationBadgeManager } from './NotificationBadgeManager';
|
|
12
|
-
import { devLog, devError } from '
|
|
12
|
+
import { devLog, devError } from '../../../../utils/devUtils';
|
|
13
13
|
import type { ScheduleNotificationOptions, ScheduledNotification } from './types';
|
|
14
14
|
|
|
15
15
|
export class NotificationManager {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Notifications from 'expo-notifications';
|
|
2
2
|
import * as Device from 'expo-device';
|
|
3
3
|
import { Platform } from 'react-native';
|
|
4
|
-
import { devWarn, devError, devLog } from '
|
|
4
|
+
import { devWarn, devError, devLog } from '../../../../utils/devUtils';
|
|
5
5
|
|
|
6
6
|
export class NotificationPermissions {
|
|
7
7
|
async requestPermissions(): Promise<boolean> {
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Notification Store
|
|
3
|
+
* Consolidates NotificationsStore, RemindersStore, and PreferencesStore
|
|
4
|
+
*
|
|
5
|
+
* Production-ready implementation with:
|
|
6
|
+
* - Single source of truth for all notification state
|
|
7
|
+
* - Proper state partializing (transient vs persistent)
|
|
8
|
+
* - Type-safe actions and selectors
|
|
9
|
+
* - Data migration support
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useMemo } from 'react';
|
|
13
|
+
import { createStore, storageService } from '@umituz/react-native-design-system';
|
|
14
|
+
import { excludeTransientState } from '../../../../infrastructure/storage/storeConfig';
|
|
15
|
+
import type { Reminder, QuietHoursConfig, NotificationPreferences } from '../services/types';
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// STATE & ACTIONS TYPES
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
interface NotificationState {
|
|
22
|
+
// Runtime state (not persisted)
|
|
23
|
+
hasPermissions: boolean;
|
|
24
|
+
isInitialized: boolean;
|
|
25
|
+
isLoading: boolean;
|
|
26
|
+
|
|
27
|
+
// Persistent state
|
|
28
|
+
preferences: NotificationPreferences;
|
|
29
|
+
reminders: Reminder[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface NotificationActions {
|
|
33
|
+
// Permission actions
|
|
34
|
+
setPermissions: (granted: boolean) => void;
|
|
35
|
+
setInitialized: (initialized: boolean) => void;
|
|
36
|
+
|
|
37
|
+
// Preference actions
|
|
38
|
+
initialize: () => void;
|
|
39
|
+
updatePreferences: (updates: Partial<NotificationPreferences>) => void;
|
|
40
|
+
updateQuietHours: (quietHours: QuietHoursConfig) => void;
|
|
41
|
+
|
|
42
|
+
// Reminder actions
|
|
43
|
+
addReminder: (reminder: Reminder) => void;
|
|
44
|
+
updateReminder: (id: string, updates: Partial<Reminder>) => void;
|
|
45
|
+
deleteReminder: (id: string) => void;
|
|
46
|
+
toggleReminder: (id: string) => void;
|
|
47
|
+
|
|
48
|
+
// Reset actions
|
|
49
|
+
reset: () => void;
|
|
50
|
+
resetReminders: () => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// DEFAULT VALUES
|
|
55
|
+
// ============================================================================
|
|
56
|
+
|
|
57
|
+
const DEFAULT_PREFERENCES: NotificationPreferences = {
|
|
58
|
+
enabled: true,
|
|
59
|
+
sound: true,
|
|
60
|
+
vibration: true,
|
|
61
|
+
quietHours: {
|
|
62
|
+
enabled: false,
|
|
63
|
+
startHour: 22,
|
|
64
|
+
startMinute: 0,
|
|
65
|
+
endHour: 7,
|
|
66
|
+
endMinute: 0,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const DEFAULT_STATE: NotificationState = {
|
|
71
|
+
// Runtime state
|
|
72
|
+
hasPermissions: false,
|
|
73
|
+
isInitialized: false,
|
|
74
|
+
isLoading: true,
|
|
75
|
+
|
|
76
|
+
// Persistent state
|
|
77
|
+
preferences: DEFAULT_PREFERENCES,
|
|
78
|
+
reminders: [],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// UNIFIED NOTIFICATION STORE
|
|
83
|
+
// ============================================================================
|
|
84
|
+
|
|
85
|
+
export const useNotificationStore = createStore<NotificationState, NotificationActions>({
|
|
86
|
+
name: 'unified-notification-store',
|
|
87
|
+
version: 1,
|
|
88
|
+
persist: true,
|
|
89
|
+
storage: storageService,
|
|
90
|
+
|
|
91
|
+
// Only persist preferences and reminders (exclude runtime state)
|
|
92
|
+
partialize: (state) => ({
|
|
93
|
+
preferences: state.preferences,
|
|
94
|
+
reminders: state.reminders,
|
|
95
|
+
}),
|
|
96
|
+
|
|
97
|
+
initialState: DEFAULT_STATE,
|
|
98
|
+
|
|
99
|
+
actions: (set, get) => ({
|
|
100
|
+
// Permission actions
|
|
101
|
+
setPermissions: (granted) => set({ hasPermissions: granted }),
|
|
102
|
+
setInitialized: (initialized) => set({ isInitialized: initialized }),
|
|
103
|
+
|
|
104
|
+
// Preference actions
|
|
105
|
+
initialize: () => {
|
|
106
|
+
set({ isLoading: false, isInitialized: true });
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
updatePreferences: (updates) => {
|
|
110
|
+
const { preferences } = get();
|
|
111
|
+
set({ preferences: { ...preferences, ...updates } });
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
updateQuietHours: (quietHours) => {
|
|
115
|
+
const { preferences } = get();
|
|
116
|
+
set({ preferences: { ...preferences, quietHours } });
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
// Reminder actions
|
|
120
|
+
addReminder: (reminder) => {
|
|
121
|
+
const { reminders } = get();
|
|
122
|
+
set({ reminders: [...reminders, reminder] });
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
updateReminder: (id, updates) => {
|
|
126
|
+
const { reminders } = get();
|
|
127
|
+
set({
|
|
128
|
+
reminders: reminders.map(r =>
|
|
129
|
+
r.id === id ? { ...r, ...updates, updatedAt: new Date().toISOString() } : r
|
|
130
|
+
),
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
deleteReminder: (id) => {
|
|
135
|
+
const { reminders } = get();
|
|
136
|
+
set({ reminders: reminders.filter(r => r.id !== id) });
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
toggleReminder: (id) => {
|
|
140
|
+
const { reminders } = get();
|
|
141
|
+
set({
|
|
142
|
+
reminders: reminders.map(r =>
|
|
143
|
+
r.id === id ? { ...r, enabled: !r.enabled, updatedAt: new Date().toISOString() } : r
|
|
144
|
+
),
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// Reset actions
|
|
149
|
+
reset: () => {
|
|
150
|
+
set({
|
|
151
|
+
preferences: DEFAULT_PREFERENCES,
|
|
152
|
+
reminders: [],
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
resetReminders: () => {
|
|
157
|
+
set({ reminders: [] });
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// ============================================================================
|
|
163
|
+
// SELECTOR HOOKS - Permissions & Initialization
|
|
164
|
+
// ============================================================================
|
|
165
|
+
|
|
166
|
+
export const useNotificationPermissions = () =>
|
|
167
|
+
useNotificationStore(state => state.hasPermissions);
|
|
168
|
+
|
|
169
|
+
export const useNotificationInitialized = () =>
|
|
170
|
+
useNotificationStore(state => state.isInitialized);
|
|
171
|
+
|
|
172
|
+
export const useRemindersLoading = () =>
|
|
173
|
+
useNotificationStore(state => state.isLoading);
|
|
174
|
+
|
|
175
|
+
export const useRemindersInitialized = () =>
|
|
176
|
+
useNotificationStore(state => state.isInitialized);
|
|
177
|
+
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// SELECTOR HOOKS - Preferences
|
|
180
|
+
// ============================================================================
|
|
181
|
+
|
|
182
|
+
export const useNotificationPreferences = () =>
|
|
183
|
+
useNotificationStore(state => state.preferences);
|
|
184
|
+
|
|
185
|
+
export const useQuietHours = () =>
|
|
186
|
+
useNotificationStore(state => state.preferences.quietHours);
|
|
187
|
+
|
|
188
|
+
// ============================================================================
|
|
189
|
+
// SELECTOR HOOKS - Reminders
|
|
190
|
+
// ============================================================================
|
|
191
|
+
|
|
192
|
+
export const useReminders = () =>
|
|
193
|
+
useNotificationStore(state => state.reminders);
|
|
194
|
+
|
|
195
|
+
export const useEnabledReminders = () => {
|
|
196
|
+
const reminders = useNotificationStore(state => state.reminders);
|
|
197
|
+
return useMemo(() => reminders.filter(r => r.enabled), [reminders]);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const useReminderById = (id: string) =>
|
|
201
|
+
useNotificationStore(state => state.reminders.find(r => r.id === id));
|
|
202
|
+
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// LEGACY COMPATIBILITY HOOK (for smooth migration)
|
|
205
|
+
// ============================================================================
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @deprecated Use useNotificationStore directly or specific selectors
|
|
209
|
+
* Kept temporarily for backward compatibility during migration
|
|
210
|
+
*/
|
|
211
|
+
export const useNotifications = () => {
|
|
212
|
+
const hasPermissions = useNotificationPermissions();
|
|
213
|
+
const isInitialized = useNotificationInitialized();
|
|
214
|
+
const setPermissions = useNotificationStore(state => state.setPermissions);
|
|
215
|
+
const setInitialized = useNotificationStore(state => state.setInitialized);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
hasPermissions,
|
|
219
|
+
isInitialized,
|
|
220
|
+
setPermissions,
|
|
221
|
+
setInitialized,
|
|
222
|
+
};
|
|
223
|
+
};
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useEffect, useCallback } from 'react';
|
|
7
|
-
import { useNotificationPreferences, useQuietHours,
|
|
7
|
+
import { useNotificationPreferences, useQuietHours, useNotificationStore, useRemindersLoading } from '../../infrastructure/storage/UnifiedNotificationStore';
|
|
8
8
|
import { notificationService } from '../../infrastructure/services/NotificationService';
|
|
9
9
|
|
|
10
10
|
export const useNotificationSettingsUI = () => {
|
|
11
11
|
const preferences = useNotificationPreferences();
|
|
12
12
|
const quietHours = useQuietHours();
|
|
13
|
-
const { initialize, updatePreferences, updateQuietHours } =
|
|
13
|
+
const { initialize, updatePreferences, updateQuietHours } = useNotificationStore();
|
|
14
14
|
const isLoading = useRemindersLoading();
|
|
15
15
|
|
|
16
16
|
useEffect(() => {
|
|
@@ -18,7 +18,7 @@ import { SettingRow } from '../components/SettingRow';
|
|
|
18
18
|
import { RemindersNavRow } from '../components/RemindersNavRow';
|
|
19
19
|
import { useNotificationSettingsUI } from '../hooks/useNotificationSettingsUI';
|
|
20
20
|
import { useTimePicker } from '../hooks/useTimePicker';
|
|
21
|
-
import { useReminders } from '../../
|
|
21
|
+
import { useReminders } from '../../infrastructure/storage/UnifiedNotificationStore';
|
|
22
22
|
import { useQuietHoursActions } from '../../quietHours/infrastructure/hooks/useQuietHoursActions';
|
|
23
23
|
import type { NotificationSettingsTranslations, QuietHoursTranslations } from '../../infrastructure/services/types';
|
|
24
24
|
import { createStyles } from './NotificationSettingsScreen.styles';
|