@umituz/react-native-settings 4.23.63 → 4.23.64
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 +2 -2
- package/src/domains/appearance/hooks/useAppearanceActions.ts +6 -4
- package/src/domains/faqs/presentation/hooks/useFAQExpansion.ts +5 -3
- package/src/domains/gamification/utils/calculations.ts +1 -2
- package/src/domains/legal/domain/services/ContentValidationService.ts +0 -50
- package/src/domains/legal/presentation/components/LegalLinks.tsx +0 -13
- package/src/domains/legal/presentation/screens/LegalContentScreen.tsx +0 -11
- package/src/domains/notifications/reminders/infrastructure/hooks/useReminderActions.ts +25 -17
- package/src/domains/notifications/reminders/infrastructure/storage/RemindersStore.ts +7 -3
- package/src/index.ts +1 -1
- package/src/infrastructure/services/SettingsService.ts +0 -6
- package/src/presentation/components/SettingsFooter.tsx +1 -1
- package/src/presentation/hooks/useSettingsScreenConfig.ts +3 -3
- package/src/presentation/navigation/SettingsStackNavigator.tsx +2 -2
- package/src/presentation/navigation/components/wrappers/SettingsScreenWrapper.tsx +2 -2
- package/src/presentation/navigation/components/wrappers/index.ts +0 -2
- package/src/presentation/navigation/types.ts +2 -2
- package/src/presentation/navigation/utils/navigationTranslations.ts +1 -1
- package/src/presentation/screens/SettingsScreen.tsx +0 -1
- package/src/presentation/screens/components/SettingsContent.tsx +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "4.23.
|
|
3
|
+
"version": "4.23.64",
|
|
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",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
77
77
|
"@typescript-eslint/parser": "^7.18.0",
|
|
78
78
|
"@umituz/react-native-auth": "^3.6.49",
|
|
79
|
-
"@umituz/react-native-design-system": "^4.23.
|
|
79
|
+
"@umituz/react-native-design-system": "^4.23.58",
|
|
80
80
|
"@umituz/react-native-firebase": "^1.13.102",
|
|
81
81
|
"@umituz/react-native-sentry": "*",
|
|
82
82
|
"eslint": "^8.57.0",
|
|
@@ -17,10 +17,12 @@ export const useAppearanceActions = () => {
|
|
|
17
17
|
}, [setThemeMode]);
|
|
18
18
|
|
|
19
19
|
const handleColorChange = useCallback((key: keyof CustomThemeColors, color: string) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
setLocalCustomColors(prev => {
|
|
21
|
+
const newColors = { ...prev, [key]: color };
|
|
22
|
+
setCustomColors(newColors);
|
|
23
|
+
return newColors;
|
|
24
|
+
});
|
|
25
|
+
}, [setCustomColors]);
|
|
24
26
|
|
|
25
27
|
const handleResetColors = useCallback(() => {
|
|
26
28
|
reset();
|
|
@@ -23,11 +23,13 @@ export function useFAQExpansion(): UseFAQExpansionResult {
|
|
|
23
23
|
|
|
24
24
|
const toggleExpansion = useCallback((itemId: string) => {
|
|
25
25
|
setExpandedItems(prev => {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const next = new Set(prev);
|
|
27
|
+
if (next.has(itemId)) {
|
|
28
|
+
next.delete(itemId);
|
|
28
29
|
} else {
|
|
29
|
-
|
|
30
|
+
next.add(itemId);
|
|
30
31
|
}
|
|
32
|
+
return next;
|
|
31
33
|
});
|
|
32
34
|
}, []);
|
|
33
35
|
|
|
@@ -45,11 +45,10 @@ export const checkAchievementUnlock = (
|
|
|
45
45
|
): boolean => {
|
|
46
46
|
switch (definition.type) {
|
|
47
47
|
case "count":
|
|
48
|
+
case "milestone":
|
|
48
49
|
return tasksCompleted >= definition.threshold;
|
|
49
50
|
case "streak":
|
|
50
51
|
return currentStreak >= definition.threshold;
|
|
51
|
-
case "milestone":
|
|
52
|
-
return tasksCompleted >= definition.threshold;
|
|
53
52
|
default:
|
|
54
53
|
return false;
|
|
55
54
|
}
|
|
@@ -4,57 +4,7 @@
|
|
|
4
4
|
* Extracted from screens to follow SOLID principles
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export interface ContentValidationRule {
|
|
8
|
-
content?: string;
|
|
9
|
-
url?: string;
|
|
10
|
-
title?: string;
|
|
11
|
-
viewOnlineText?: string;
|
|
12
|
-
openText?: string;
|
|
13
|
-
privacyText?: string;
|
|
14
|
-
termsText?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
7
|
export class ContentValidationService {
|
|
18
|
-
/**
|
|
19
|
-
* Validate screen content requirements
|
|
20
|
-
*/
|
|
21
|
-
static validateScreenContent(
|
|
22
|
-
_content: string | undefined,
|
|
23
|
-
_url: string | undefined,
|
|
24
|
-
_title: string | undefined,
|
|
25
|
-
_viewOnlineText: string | undefined,
|
|
26
|
-
_openText: string | undefined,
|
|
27
|
-
_screenName: string
|
|
28
|
-
): void {
|
|
29
|
-
// Silent validation - no console output
|
|
30
|
-
void _content;
|
|
31
|
-
void _url;
|
|
32
|
-
void _title;
|
|
33
|
-
void _viewOnlineText;
|
|
34
|
-
void _openText;
|
|
35
|
-
void _screenName;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Validate legal links requirements
|
|
40
|
-
*/
|
|
41
|
-
static validateLegalLinks(
|
|
42
|
-
_privacyPolicyUrl: string | undefined,
|
|
43
|
-
_termsOfServiceUrl: string | undefined,
|
|
44
|
-
_privacyText: string | undefined,
|
|
45
|
-
_termsText: string | undefined,
|
|
46
|
-
_onPrivacyPress: (() => void) | undefined,
|
|
47
|
-
_onTermsPress: (() => void) | undefined
|
|
48
|
-
): void {
|
|
49
|
-
// Silent validation - no console output
|
|
50
|
-
void _privacyPolicyUrl;
|
|
51
|
-
void _termsOfServiceUrl;
|
|
52
|
-
void _privacyText;
|
|
53
|
-
void _termsText;
|
|
54
|
-
void _onPrivacyPress;
|
|
55
|
-
void _onTermsPress;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
8
|
/**
|
|
59
9
|
* Check if content is valid
|
|
60
10
|
*/
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import React from "react";
|
|
8
8
|
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
9
9
|
import { AtomicText } from "@umituz/react-native-design-system";
|
|
10
|
-
import { ContentValidationService } from "../../domain/services/ContentValidationService";
|
|
11
10
|
import { UrlHandlerService } from "../../domain/services/UrlHandlerService";
|
|
12
11
|
|
|
13
12
|
export interface LegalLinksProps {
|
|
@@ -51,18 +50,6 @@ export const LegalLinks: React.FC<LegalLinksProps> = React.memo(
|
|
|
51
50
|
onTermsPress,
|
|
52
51
|
style,
|
|
53
52
|
}) => {
|
|
54
|
-
// Validate required props
|
|
55
|
-
React.useEffect(() => {
|
|
56
|
-
ContentValidationService.validateLegalLinks(
|
|
57
|
-
privacyPolicyUrl,
|
|
58
|
-
termsOfServiceUrl,
|
|
59
|
-
privacyText,
|
|
60
|
-
termsText,
|
|
61
|
-
onPrivacyPress,
|
|
62
|
-
onTermsPress
|
|
63
|
-
);
|
|
64
|
-
}, [privacyPolicyUrl, termsOfServiceUrl, privacyText, termsText, onPrivacyPress, onTermsPress]);
|
|
65
|
-
|
|
66
53
|
// Memoize press handlers to prevent child re-renders
|
|
67
54
|
const handlePrivacyPress = React.useCallback(async () => {
|
|
68
55
|
if (onPrivacyPress) {
|
|
@@ -37,17 +37,6 @@ export const LegalContentScreen: React.FC<LegalContentScreenProps> = React.memo(
|
|
|
37
37
|
const tokens = useAppDesignTokens();
|
|
38
38
|
const insets = useSafeAreaInsets();
|
|
39
39
|
|
|
40
|
-
React.useEffect(() => {
|
|
41
|
-
ContentValidationService.validateScreenContent(
|
|
42
|
-
content,
|
|
43
|
-
url,
|
|
44
|
-
title,
|
|
45
|
-
viewOnlineText,
|
|
46
|
-
openText,
|
|
47
|
-
styleCacheKey
|
|
48
|
-
);
|
|
49
|
-
}, [content, url, title, viewOnlineText, openText, styleCacheKey]);
|
|
50
|
-
|
|
51
40
|
const styles = React.useMemo(() => {
|
|
52
41
|
const cacheKey = StyleCacheService.createTokenCacheKey(tokens);
|
|
53
42
|
return StyleCacheService.getCachedStyles(
|
|
@@ -25,15 +25,19 @@ export const useReminderActions = () => {
|
|
|
25
25
|
updatedAt: now,
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
try {
|
|
29
|
+
const trigger = buildTrigger(reminder);
|
|
30
|
+
const notificationId = await scheduler.scheduleNotification({
|
|
31
|
+
title: reminder.title,
|
|
32
|
+
body: reminder.body,
|
|
33
|
+
trigger,
|
|
34
|
+
data: { reminderId: reminder.id },
|
|
35
|
+
});
|
|
36
|
+
reminder.notificationId = notificationId;
|
|
37
|
+
} catch {
|
|
38
|
+
reminder.enabled = false;
|
|
39
|
+
}
|
|
40
|
+
|
|
37
41
|
await addReminder(reminder);
|
|
38
42
|
|
|
39
43
|
return reminder;
|
|
@@ -86,14 +90,18 @@ export const useReminderActions = () => {
|
|
|
86
90
|
await scheduler.cancelNotification(reminder.notificationId);
|
|
87
91
|
await updateReminder(id, { enabled: false, notificationId: undefined });
|
|
88
92
|
} else if (!reminder.enabled) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
try {
|
|
94
|
+
const trigger = buildTrigger(reminder);
|
|
95
|
+
const notificationId = await scheduler.scheduleNotification({
|
|
96
|
+
title: reminder.title,
|
|
97
|
+
body: reminder.body,
|
|
98
|
+
trigger,
|
|
99
|
+
data: { reminderId: reminder.id },
|
|
100
|
+
});
|
|
101
|
+
await updateReminder(id, { enabled: true, notificationId });
|
|
102
|
+
} catch {
|
|
103
|
+
await updateReminder(id, { enabled: false });
|
|
104
|
+
}
|
|
97
105
|
}
|
|
98
106
|
}, [updateReminder]);
|
|
99
107
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Manages reminder state with AsyncStorage persistence
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { useMemo } from 'react';
|
|
6
7
|
import { createStore } from '@umituz/react-native-design-system';
|
|
7
8
|
import type { Reminder, QuietHoursConfig, NotificationPreferences } from '../../../infrastructure/services/types';
|
|
8
9
|
|
|
@@ -76,7 +77,7 @@ interface PreferencesState {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
interface PreferencesActions {
|
|
79
|
-
initialize: () =>
|
|
80
|
+
initialize: () => void;
|
|
80
81
|
updatePreferences: (updates: Partial<NotificationPreferences>) => void;
|
|
81
82
|
updateQuietHours: (quietHours: QuietHoursConfig) => void;
|
|
82
83
|
reset: () => void;
|
|
@@ -110,7 +111,7 @@ export const usePreferencesStore = createStore<PreferencesState, PreferencesActi
|
|
|
110
111
|
initialState: initialPreferencesState,
|
|
111
112
|
persist: true,
|
|
112
113
|
actions: (set, get) => ({
|
|
113
|
-
initialize:
|
|
114
|
+
initialize: () => {
|
|
114
115
|
set({ isLoading: false, isInitialized: true });
|
|
115
116
|
},
|
|
116
117
|
|
|
@@ -135,7 +136,10 @@ export const usePreferencesStore = createStore<PreferencesState, PreferencesActi
|
|
|
135
136
|
// ============================================================================
|
|
136
137
|
|
|
137
138
|
export const useReminders = () => useRemindersStore(state => state.reminders);
|
|
138
|
-
export const useEnabledReminders = () =>
|
|
139
|
+
export const useEnabledReminders = () => {
|
|
140
|
+
const reminders = useRemindersStore(state => state.reminders);
|
|
141
|
+
return useMemo(() => reminders.filter(r => r.enabled), [reminders]);
|
|
142
|
+
};
|
|
139
143
|
export const useReminderById = (id: string) => useRemindersStore(state => state.reminders.find(r => r.id === id));
|
|
140
144
|
|
|
141
145
|
// ============================================================================
|
package/src/index.ts
CHANGED
|
@@ -9,17 +9,11 @@ import type { UserSettings, SettingsResult } from '../../application/ports/ISett
|
|
|
9
9
|
|
|
10
10
|
export class SettingsService {
|
|
11
11
|
private repository: SettingsRepository;
|
|
12
|
-
private initialized: boolean = false;
|
|
13
12
|
|
|
14
13
|
constructor() {
|
|
15
14
|
this.repository = new SettingsRepository();
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
async initialize(): Promise<void> {
|
|
19
|
-
if (this.initialized) return;
|
|
20
|
-
this.initialized = true;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
17
|
async getSettings(userId: string): Promise<SettingsResult<UserSettings>> {
|
|
24
18
|
return this.repository.getSettings(userId);
|
|
25
19
|
}
|
|
@@ -17,7 +17,7 @@ export const SettingsFooter: React.FC<SettingsFooterProps> = ({
|
|
|
17
17
|
|
|
18
18
|
const displayText = versionText || (appVersion && versionLabel
|
|
19
19
|
? `${versionLabel} ${appVersion}`
|
|
20
|
-
: appVersion
|
|
20
|
+
: appVersion);
|
|
21
21
|
|
|
22
22
|
if (!displayText) return null;
|
|
23
23
|
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
} from "../utils/config-creators";
|
|
30
30
|
import type { SettingsConfig } from "../screens/types";
|
|
31
31
|
import type { FeedbackFormData } from "../utils/config-creators";
|
|
32
|
-
import type { AppInfo, FAQData,
|
|
32
|
+
import type { AppInfo, FAQData, UserProfileDisplay, AdditionalScreen } from "../navigation/types";
|
|
33
33
|
import type { AccountScreenConfig } from "@umituz/react-native-auth";
|
|
34
34
|
|
|
35
35
|
export interface SettingsFeatures {
|
|
@@ -54,7 +54,7 @@ export interface UseSettingsScreenConfigParams {
|
|
|
54
54
|
|
|
55
55
|
export interface SettingsScreenConfigResult {
|
|
56
56
|
settingsConfig: SettingsConfig;
|
|
57
|
-
userProfile:
|
|
57
|
+
userProfile: UserProfileDisplay;
|
|
58
58
|
accountConfig: AccountScreenConfig;
|
|
59
59
|
translatedFaqData: FAQData | undefined;
|
|
60
60
|
isLoading: boolean;
|
|
@@ -127,7 +127,7 @@ export const useSettingsScreenConfig = (
|
|
|
127
127
|
showAbout, showLegal, showFaqs, showRating,
|
|
128
128
|
]);
|
|
129
129
|
|
|
130
|
-
const userProfile = useMemo(():
|
|
130
|
+
const userProfile = useMemo((): UserProfileDisplay => {
|
|
131
131
|
const isAnonymous = userProfileData?.isAnonymous ?? true;
|
|
132
132
|
return {
|
|
133
133
|
displayName: userProfileData?.displayName || t("settings.profile.anonymousName"),
|
|
@@ -10,11 +10,11 @@ import { useAppDesignTokens, StackNavigator, type StackScreen, type StackNavigat
|
|
|
10
10
|
import { useLocalization, LanguageSelectionScreen } from "../../domains/localization";
|
|
11
11
|
import { NotificationSettingsScreen } from "../../domains/notifications";
|
|
12
12
|
import { AccountScreen } from "@umituz/react-native-auth";
|
|
13
|
+
import { SettingsScreen } from "../screens/SettingsScreen";
|
|
13
14
|
import { AppearanceScreen } from "../screens/AppearanceScreen";
|
|
14
15
|
import { FAQScreen } from "../../domains/faqs";
|
|
15
16
|
import { useNavigationHandlers } from "./hooks";
|
|
16
17
|
import {
|
|
17
|
-
SettingsScreenWrapper,
|
|
18
18
|
LegalScreenWrapper,
|
|
19
19
|
AboutScreenWrapper,
|
|
20
20
|
} from "./components/wrappers";
|
|
@@ -79,7 +79,7 @@ export const SettingsStackNavigator: React.FC<SettingsStackNavigatorProps> = ({
|
|
|
79
79
|
name: "SettingsMain",
|
|
80
80
|
options: { headerShown: false },
|
|
81
81
|
children: () => (
|
|
82
|
-
<
|
|
82
|
+
<SettingsScreen
|
|
83
83
|
config={config}
|
|
84
84
|
appVersion={appInfo.version}
|
|
85
85
|
showUserProfile={showUserProfile}
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
import React from "react";
|
|
5
5
|
import { SettingsScreen } from "../../../screens/SettingsScreen";
|
|
6
6
|
import type { SettingsConfig, CustomSettingsSection } from "../../../screens/types";
|
|
7
|
-
import type {
|
|
7
|
+
import type { UserProfileDisplay } from "../../types";
|
|
8
8
|
import type { DevSettingsProps } from "../../../../domains/dev";
|
|
9
9
|
|
|
10
10
|
export interface SettingsScreenWrapperProps {
|
|
11
11
|
config?: SettingsConfig;
|
|
12
12
|
appVersion: string;
|
|
13
13
|
showUserProfile: boolean;
|
|
14
|
-
userProfile?:
|
|
14
|
+
userProfile?: UserProfileDisplay;
|
|
15
15
|
devSettings?: DevSettingsProps;
|
|
16
16
|
customSections?: CustomSettingsSection[];
|
|
17
17
|
showHeader?: boolean;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wrapper Components
|
|
3
3
|
*/
|
|
4
|
-
export { SettingsScreenWrapper } from './SettingsScreenWrapper';
|
|
5
|
-
export type { SettingsScreenWrapperProps } from './SettingsScreenWrapper';
|
|
6
4
|
export { LegalScreenWrapper } from './LegalScreenWrapper';
|
|
7
5
|
export type { LegalScreenWrapperProps } from './LegalScreenWrapper';
|
|
8
6
|
export { AboutScreenWrapper } from './AboutScreenWrapper';
|
|
@@ -49,7 +49,7 @@ export type SettingsStackParamList = {
|
|
|
49
49
|
/**
|
|
50
50
|
* User profile configuration
|
|
51
51
|
*/
|
|
52
|
-
export interface
|
|
52
|
+
export interface UserProfileDisplay {
|
|
53
53
|
displayName?: string;
|
|
54
54
|
userId?: string;
|
|
55
55
|
isAnonymous?: boolean;
|
|
@@ -93,7 +93,7 @@ export interface SettingsStackNavigatorProps {
|
|
|
93
93
|
faqData?: FAQData;
|
|
94
94
|
config?: SettingsConfig;
|
|
95
95
|
showUserProfile?: boolean;
|
|
96
|
-
userProfile?:
|
|
96
|
+
userProfile?: UserProfileDisplay;
|
|
97
97
|
accountConfig?: AccountConfig;
|
|
98
98
|
additionalScreens?: AdditionalScreen[];
|
|
99
99
|
devSettings?: DevSettingsProps;
|
|
@@ -26,7 +26,7 @@ export const createQuietHoursTranslations = (t: (key: string) => string) => ({
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
export const createLegalScreenProps = (
|
|
29
|
-
t:
|
|
29
|
+
t: (key: string) => string,
|
|
30
30
|
handlePrivacyPress: () => void,
|
|
31
31
|
handleTermsPress: () => void,
|
|
32
32
|
handleEulaPress: () => void
|
|
@@ -92,7 +92,6 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
|
|
|
92
92
|
footerText={footerText}
|
|
93
93
|
appVersion={appVersion}
|
|
94
94
|
customSections={customSections}
|
|
95
|
-
showCloseButton={showCloseButton}
|
|
96
95
|
devSettings={devSettings}
|
|
97
96
|
gamificationConfig={gamificationConfig}
|
|
98
97
|
/>
|
|
@@ -50,7 +50,6 @@ interface SettingsContentProps {
|
|
|
50
50
|
footerText?: string;
|
|
51
51
|
appVersion?: string;
|
|
52
52
|
customSections?: CustomSettingsSection[];
|
|
53
|
-
showCloseButton?: boolean;
|
|
54
53
|
emptyStateText?: string;
|
|
55
54
|
devSettings?: DevSettingsProps;
|
|
56
55
|
gamificationConfig?: import("../../../domains/gamification").GamificationSettingsConfig;
|
|
@@ -65,7 +64,6 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
|
|
|
65
64
|
footerText,
|
|
66
65
|
appVersion,
|
|
67
66
|
customSections = [],
|
|
68
|
-
showCloseButton = false,
|
|
69
67
|
emptyStateText,
|
|
70
68
|
devSettings,
|
|
71
69
|
gamificationConfig,
|