@umituz/react-native-settings 5.3.48 → 5.3.50
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/dist/domains/gamification/utils/calculations.d.ts +5 -0
- package/dist/presentation/navigation/hooks/useSettingsScreens.d.ts +1 -0
- package/dist/presentation/screens/SettingsScreen.d.ts +2 -0
- package/dist/presentation/screens/components/sections/CustomSettingsList.d.ts +1 -0
- package/dist/presentation/screens/components/types/SettingsContentProps.d.ts +1 -0
- package/package.json +1 -1
- package/src/domains/about/presentation/hooks/useAboutInfo.ts +4 -1
- package/src/domains/feedback/presentation/screens/FeatureRequestScreen.tsx +1 -1
- package/src/domains/gamification/components/GamificationScreen/GamificationScreen.tsx +1 -1
- package/src/domains/gamification/utils/calculations.ts +13 -4
- package/src/domains/localization/infrastructure/config/I18nInitializer.ts +4 -1
- package/src/domains/localization/infrastructure/storage/LanguageSwitcher.ts +3 -1
- package/src/domains/notifications/infrastructure/services/NotificationScheduler.ts +13 -4
- package/src/domains/notifications/infrastructure/services/NotificationService.ts +4 -1
- package/src/domains/notifications/reminders/infrastructure/hooks/useReminderActions.ts +4 -1
- package/src/domains/rating/application/services/RatingService.ts +16 -5
- package/src/domains/rating/infrastructure/storage/RatingStorage.ts +16 -5
- package/src/presentation/hooks/useSettingsScreenConfig.ts +10 -2
- package/src/presentation/navigation/SettingsStackNavigator.tsx +5 -0
- package/src/presentation/navigation/hooks/useNavigationHandlers.ts +19 -6
- package/src/presentation/navigation/hooks/useSettingsScreens.ts +8 -4
- package/src/presentation/screens/SettingsScreen.tsx +4 -0
- package/src/presentation/screens/components/SettingsContent.tsx +2 -1
- package/src/presentation/screens/components/sections/CustomSettingsList.tsx +1 -0
- package/src/presentation/screens/components/types/SettingsContentProps.ts +1 -0
- package/src/presentation/utils/screenFactory.ts +8 -6
|
@@ -6,5 +6,10 @@ import type { LevelDefinition, LevelState, AchievementDefinition } from "../type
|
|
|
6
6
|
export declare const calculateLevel: (points: number, levels: LevelDefinition[]) => LevelState;
|
|
7
7
|
export declare const checkAchievementUnlock: (definition: AchievementDefinition, tasksCompleted: number, currentStreak: number) => boolean;
|
|
8
8
|
export declare const updateAchievementProgress: (definition: AchievementDefinition, tasksCompleted: number, currentStreak: number) => number;
|
|
9
|
+
/**
|
|
10
|
+
* Check if streak is active based on last activity date
|
|
11
|
+
* Returns true if last activity was today or yesterday
|
|
12
|
+
* FIXED: Now uses date comparison instead of hour-based calculation to avoid timezone issues
|
|
13
|
+
*/
|
|
9
14
|
export declare const isStreakActive: (lastActivityDate: string | null) => boolean;
|
|
10
15
|
export declare const isSameDay: (date1: Date, date2: Date) => boolean;
|
|
@@ -8,5 +8,6 @@ export interface UseSettingsScreensProps extends SettingsStackNavigatorProps {
|
|
|
8
8
|
legalProps: LegalScreenProps;
|
|
9
9
|
notificationTranslations: NotificationSettingsTranslations;
|
|
10
10
|
quietHoursTranslations: QuietHoursTranslations;
|
|
11
|
+
navigation?: any;
|
|
11
12
|
}
|
|
12
13
|
export declare const useSettingsScreens: (props: UseSettingsScreensProps) => StackScreen[];
|
|
@@ -45,5 +45,7 @@ export interface SettingsScreenProps {
|
|
|
45
45
|
showHeader?: boolean;
|
|
46
46
|
/** Password prompt modal component */
|
|
47
47
|
PasswordPromptComponent?: React.ReactNode;
|
|
48
|
+
/** Navigation object for custom sections */
|
|
49
|
+
navigation?: any;
|
|
48
50
|
}
|
|
49
51
|
export declare const SettingsScreen: React.FC<SettingsScreenProps>;
|
|
@@ -2,6 +2,7 @@ import React from "react";
|
|
|
2
2
|
import type { CustomSettingsSection } from "../../types";
|
|
3
3
|
interface CustomSettingsListProps {
|
|
4
4
|
customSections?: CustomSettingsSection[];
|
|
5
|
+
navigation?: any;
|
|
5
6
|
}
|
|
6
7
|
export declare const CustomSettingsList: React.FC<CustomSettingsListProps>;
|
|
7
8
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.50",
|
|
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": "./dist/index.d.ts",
|
|
@@ -24,6 +24,7 @@ export const useAboutInfo = (
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Initialize app info with config
|
|
27
|
+
* FIXED: Set isInitializedRef BEFORE async operation to prevent race condition
|
|
27
28
|
*/
|
|
28
29
|
const initialize = useCallback(
|
|
29
30
|
async (config: AboutConfig) => {
|
|
@@ -31,10 +32,12 @@ export const useAboutInfo = (
|
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
// FIX: Set ref immediately to prevent race condition from concurrent calls
|
|
36
|
+
isInitializedRef.current = true;
|
|
37
|
+
|
|
34
38
|
await execute(async () => {
|
|
35
39
|
const defaultAppInfo = createDefaultAppInfo(config);
|
|
36
40
|
await repository.saveAppInfo(defaultAppInfo);
|
|
37
|
-
isInitializedRef.current = true;
|
|
38
41
|
return defaultAppInfo;
|
|
39
42
|
});
|
|
40
43
|
},
|
|
@@ -15,7 +15,7 @@ import { ScreenLayout } from "@umituz/react-native-design-system/layouts";
|
|
|
15
15
|
import { FeedbackModal } from "../components/FeedbackModal";
|
|
16
16
|
import { useFeatureRequests } from "../../infrastructure/useFeatureRequests";
|
|
17
17
|
import type { FeedbackRating } from "../../domain/entities/FeedbackEntity";
|
|
18
|
-
import type { FeatureRequestItem
|
|
18
|
+
import type { FeatureRequestItem } from "../../domain/entities/FeatureRequestEntity";
|
|
19
19
|
import type { FeedbackFormTexts } from "../components/FeedbackFormProps";
|
|
20
20
|
|
|
21
21
|
interface FeatureRequestScreenProps {
|
|
@@ -21,7 +21,7 @@ import type { GamificationScreenProps } from "./types";
|
|
|
21
21
|
* Internal component that renders the screen with all props
|
|
22
22
|
*/
|
|
23
23
|
export const GamificationScreenInner: React.FC<GamificationScreenProps> = ({
|
|
24
|
-
title,
|
|
24
|
+
title: _title,
|
|
25
25
|
statsTitle,
|
|
26
26
|
achievementsTitle,
|
|
27
27
|
streakTitle,
|
|
@@ -63,16 +63,25 @@ export const updateAchievementProgress = (
|
|
|
63
63
|
return Math.min(100, (value / definition.threshold) * 100);
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Check if streak is active based on last activity date
|
|
68
|
+
* Returns true if last activity was today or yesterday
|
|
69
|
+
* FIXED: Now uses date comparison instead of hour-based calculation to avoid timezone issues
|
|
70
|
+
*/
|
|
66
71
|
export const isStreakActive = (lastActivityDate: string | null): boolean => {
|
|
67
72
|
if (!lastActivityDate) return false;
|
|
68
73
|
|
|
69
74
|
const last = new Date(lastActivityDate);
|
|
70
75
|
const now = new Date();
|
|
71
|
-
const diffDays = Math.floor(
|
|
72
|
-
(now.getTime() - last.getTime()) / (1000 * 60 * 60 * 24)
|
|
73
|
-
);
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
// Normalize to midnight to compare days, not hours
|
|
78
|
+
const lastMidnight = new Date(last.getFullYear(), last.getMonth(), last.getDate());
|
|
79
|
+
const nowMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
80
|
+
const yesterdayMidnight = new Date(nowMidnight);
|
|
81
|
+
yesterdayMidnight.setDate(yesterdayMidnight.getDate() - 1);
|
|
82
|
+
|
|
83
|
+
// Active if last activity was today or yesterday
|
|
84
|
+
return lastMidnight.getTime() >= yesterdayMidnight.getTime();
|
|
76
85
|
};
|
|
77
86
|
|
|
78
87
|
export const isSameDay = (date1: Date, date2: Date): boolean => {
|
|
@@ -10,6 +10,7 @@ import { initReactI18next } from 'react-i18next';
|
|
|
10
10
|
import { DEFAULT_LANGUAGE } from './languages';
|
|
11
11
|
import { ResourceBuilder } from './ResourceBuilder';
|
|
12
12
|
import { NamespaceResolver } from './NamespaceResolver';
|
|
13
|
+
import { isDev } from '../../../../utils/devUtils';
|
|
13
14
|
|
|
14
15
|
export class I18nInitializer {
|
|
15
16
|
private static reactI18nextInitialized = false;
|
|
@@ -52,7 +53,9 @@ export class I18nInitializer {
|
|
|
52
53
|
returnNull: false,
|
|
53
54
|
});
|
|
54
55
|
} catch (error) {
|
|
55
|
-
|
|
56
|
+
if (isDev()) {
|
|
57
|
+
console.error('[I18nInitializer] Failed to initialize i18n:', languageCode, error);
|
|
58
|
+
}
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
|
|
@@ -47,7 +47,9 @@ export class LanguageSwitcher {
|
|
|
47
47
|
isRTL: language?.isRTL ?? false,
|
|
48
48
|
};
|
|
49
49
|
} catch (error) {
|
|
50
|
-
|
|
50
|
+
if (isDev()) {
|
|
51
|
+
console.error('[LanguageSwitcher] Failed to switch language:', languageCode, error);
|
|
52
|
+
}
|
|
51
53
|
throw error;
|
|
52
54
|
}
|
|
53
55
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as Notifications from 'expo-notifications';
|
|
2
|
+
import { isDev } from "../../../../utils/devUtils";
|
|
2
3
|
import type { ScheduleNotificationOptions, ScheduledNotification } from './types';
|
|
3
4
|
|
|
4
5
|
export class NotificationScheduler {
|
|
@@ -54,7 +55,9 @@ export class NotificationScheduler {
|
|
|
54
55
|
|
|
55
56
|
return notificationId;
|
|
56
57
|
} catch (error) {
|
|
57
|
-
|
|
58
|
+
if (isDev()) {
|
|
59
|
+
console.error('Failed to schedule notification:', error);
|
|
60
|
+
}
|
|
58
61
|
throw new Error(`Notification scheduling failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
59
62
|
}
|
|
60
63
|
}
|
|
@@ -63,7 +66,9 @@ export class NotificationScheduler {
|
|
|
63
66
|
try {
|
|
64
67
|
await Notifications.cancelScheduledNotificationAsync(notificationId);
|
|
65
68
|
} catch (error) {
|
|
66
|
-
|
|
69
|
+
if (isDev()) {
|
|
70
|
+
console.error('Failed to cancel notification:', notificationId, error);
|
|
71
|
+
}
|
|
67
72
|
// Don't throw - canceling a non-existent notification is not critical
|
|
68
73
|
}
|
|
69
74
|
}
|
|
@@ -72,7 +77,9 @@ export class NotificationScheduler {
|
|
|
72
77
|
try {
|
|
73
78
|
await Notifications.cancelAllScheduledNotificationsAsync();
|
|
74
79
|
} catch (error) {
|
|
75
|
-
|
|
80
|
+
if (isDev()) {
|
|
81
|
+
console.error('Failed to cancel all notifications:', error);
|
|
82
|
+
}
|
|
76
83
|
throw new Error(`Failed to cancel notifications: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
77
84
|
}
|
|
78
85
|
}
|
|
@@ -90,7 +97,9 @@ export class NotificationScheduler {
|
|
|
90
97
|
trigger: notification.trigger,
|
|
91
98
|
}));
|
|
92
99
|
} catch (error) {
|
|
93
|
-
|
|
100
|
+
if (isDev()) {
|
|
101
|
+
console.error('Failed to get scheduled notifications:', error);
|
|
102
|
+
}
|
|
94
103
|
return []; // Return empty array as fallback
|
|
95
104
|
}
|
|
96
105
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { NotificationManager } from './NotificationManager';
|
|
11
|
+
import { isDev } from '../../../../utils/devUtils';
|
|
11
12
|
|
|
12
13
|
export * from './types';
|
|
13
14
|
|
|
@@ -31,7 +32,9 @@ export class NotificationService {
|
|
|
31
32
|
NotificationManager.configure();
|
|
32
33
|
this.isConfigured = true;
|
|
33
34
|
} catch (error) {
|
|
34
|
-
|
|
35
|
+
if (isDev()) {
|
|
36
|
+
console.error('[NotificationService] Failed to configure NotificationManager:', error);
|
|
37
|
+
}
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
}
|
|
@@ -119,8 +119,11 @@ export const useReminderActions = () => {
|
|
|
119
119
|
throw error; // Re-throw to allow caller to handle
|
|
120
120
|
}
|
|
121
121
|
} else if (!reminder.enabled) {
|
|
122
|
+
// FIX: Create temporary updated reminder for trigger building
|
|
123
|
+
// This prevents state inconsistency where trigger is built with old state
|
|
124
|
+
const tempEnabledReminder = { ...reminder, enabled: true };
|
|
122
125
|
try {
|
|
123
|
-
const trigger = buildTrigger(
|
|
126
|
+
const trigger = buildTrigger(tempEnabledReminder);
|
|
124
127
|
const notificationId = await scheduler.scheduleNotification({
|
|
125
128
|
title: reminder.title,
|
|
126
129
|
body: reminder.body,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { RatingConfig, RatingState } from "../../domain/entities/RatingConfig";
|
|
7
|
+
import { isDev } from "../../../../utils/devUtils";
|
|
7
8
|
import {
|
|
8
9
|
getEventCount,
|
|
9
10
|
incrementEventCount,
|
|
@@ -40,7 +41,9 @@ export async function trackEvent(eventType: string): Promise<void> {
|
|
|
40
41
|
try {
|
|
41
42
|
await incrementEventCount(eventType);
|
|
42
43
|
} catch (error) {
|
|
43
|
-
|
|
44
|
+
if (isDev()) {
|
|
45
|
+
console.error('[RatingService] Failed to track event:', eventType, error);
|
|
46
|
+
}
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
|
|
@@ -90,7 +93,9 @@ export async function markPromptShown(eventType: string): Promise<void> {
|
|
|
90
93
|
try {
|
|
91
94
|
await setLastPromptDate(eventType, toISOString());
|
|
92
95
|
} catch (error) {
|
|
93
|
-
|
|
96
|
+
if (isDev()) {
|
|
97
|
+
console.error('[RatingService] Failed to mark prompt shown:', eventType, error);
|
|
98
|
+
}
|
|
94
99
|
}
|
|
95
100
|
}
|
|
96
101
|
|
|
@@ -101,7 +106,9 @@ export async function markRated(): Promise<void> {
|
|
|
101
106
|
try {
|
|
102
107
|
await setHasRated(true);
|
|
103
108
|
} catch (error) {
|
|
104
|
-
|
|
109
|
+
if (isDev()) {
|
|
110
|
+
console.error('[RatingService] Failed to mark as rated:', error);
|
|
111
|
+
}
|
|
105
112
|
}
|
|
106
113
|
}
|
|
107
114
|
|
|
@@ -112,7 +119,9 @@ export async function markDismissed(): Promise<void> {
|
|
|
112
119
|
try {
|
|
113
120
|
await setDismissed(true);
|
|
114
121
|
} catch (error) {
|
|
115
|
-
|
|
122
|
+
if (isDev()) {
|
|
123
|
+
console.error('[RatingService] Failed to mark as dismissed:', error);
|
|
124
|
+
}
|
|
116
125
|
}
|
|
117
126
|
}
|
|
118
127
|
|
|
@@ -130,6 +139,8 @@ export async function reset(eventType?: string): Promise<void> {
|
|
|
130
139
|
try {
|
|
131
140
|
await resetStorage(eventType);
|
|
132
141
|
} catch (error) {
|
|
133
|
-
|
|
142
|
+
if (isDev()) {
|
|
143
|
+
console.error('[RatingService] Failed to reset:', eventType, error);
|
|
144
|
+
}
|
|
134
145
|
}
|
|
135
146
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { storageRepository, unwrap } from "@umituz/react-native-design-system/storage";
|
|
7
|
+
import { isDev } from "../../../../utils/devUtils";
|
|
7
8
|
import type { RatingState } from "../../domain/entities/RatingConfig";
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -36,7 +37,9 @@ export async function setEventCount(eventType: string, count: number): Promise<v
|
|
|
36
37
|
try {
|
|
37
38
|
await storageRepository.setString(KEYS.eventCount(eventType), count.toString());
|
|
38
39
|
} catch (error) {
|
|
39
|
-
|
|
40
|
+
if (isDev()) {
|
|
41
|
+
console.error('[RatingStorage] Failed to set event count:', eventType, count, error);
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -78,7 +81,9 @@ export async function setLastPromptDate(eventType: string, date: string): Promis
|
|
|
78
81
|
try {
|
|
79
82
|
await storageRepository.setString(KEYS.lastPrompt(eventType), date);
|
|
80
83
|
} catch (error) {
|
|
81
|
-
|
|
84
|
+
if (isDev()) {
|
|
85
|
+
console.error('[RatingStorage] Failed to set last prompt date:', eventType, date, error);
|
|
86
|
+
}
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
|
|
@@ -101,7 +106,9 @@ export async function setHasRated(value: boolean): Promise<void> {
|
|
|
101
106
|
try {
|
|
102
107
|
await storageRepository.setString(KEYS.hasRated, value.toString());
|
|
103
108
|
} catch (error) {
|
|
104
|
-
|
|
109
|
+
if (isDev()) {
|
|
110
|
+
console.error('[RatingStorage] Failed to set has rated:', value, error);
|
|
111
|
+
}
|
|
105
112
|
}
|
|
106
113
|
}
|
|
107
114
|
|
|
@@ -124,7 +131,9 @@ export async function setDismissed(value: boolean): Promise<void> {
|
|
|
124
131
|
try {
|
|
125
132
|
await storageRepository.setString(KEYS.dismissed, value.toString());
|
|
126
133
|
} catch (error) {
|
|
127
|
-
|
|
134
|
+
if (isDev()) {
|
|
135
|
+
console.error('[RatingStorage] Failed to set dismissed:', value, error);
|
|
136
|
+
}
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
|
|
@@ -165,6 +174,8 @@ export async function reset(eventType?: string): Promise<void> {
|
|
|
165
174
|
);
|
|
166
175
|
}
|
|
167
176
|
} catch (error) {
|
|
168
|
-
|
|
177
|
+
if (isDev()) {
|
|
178
|
+
console.error('[RatingStorage] Failed to reset rating data:', eventType, error);
|
|
179
|
+
}
|
|
169
180
|
}
|
|
170
181
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { useMemo } from "react";
|
|
10
10
|
import { useAuth, useUserProfile, useAuthHandlers } from "@umituz/react-native-auth";
|
|
11
11
|
import { createUserProfileDisplay } from "../utils/userProfileUtils";
|
|
12
|
-
import { createAccountConfig } from "../utils/accountConfigUtils";
|
|
12
|
+
import { createAccountConfig, type AccountTranslations } from "../utils/accountConfigUtils";
|
|
13
13
|
import { useSettingsConfigFactory } from "../utils/settingsConfigFactory";
|
|
14
14
|
import type { SettingsConfig, SettingsTranslations } from "../screens/types";
|
|
15
15
|
import type { FeedbackFormData } from "../utils/config-creators";
|
|
@@ -147,7 +147,15 @@ export const useSettingsScreenConfig = (
|
|
|
147
147
|
onSignIn: handleSignIn,
|
|
148
148
|
onLogout: handleSignOut,
|
|
149
149
|
onDeleteAccount: handleDeleteAccount,
|
|
150
|
-
translations: translations?.account
|
|
150
|
+
translations: (translations?.account?.logout &&
|
|
151
|
+
translations?.account?.deleteAccount &&
|
|
152
|
+
translations?.account?.logoutConfirmTitle &&
|
|
153
|
+
translations?.account?.logoutConfirmMessage &&
|
|
154
|
+
translations?.account?.deleteConfirmTitle &&
|
|
155
|
+
translations?.account?.deleteConfirmMessage &&
|
|
156
|
+
translations?.account?.cancel)
|
|
157
|
+
? translations.account as AccountTranslations
|
|
158
|
+
: undefined,
|
|
151
159
|
}), [user, userProfileData, handleSignIn, handleSignOut, handleDeleteAccount, translations]);
|
|
152
160
|
|
|
153
161
|
return {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React from "react";
|
|
9
|
+
import { useNavigation } from "@react-navigation/native";
|
|
9
10
|
import { StackNavigator, type StackNavigatorConfig } from "@umituz/react-native-design-system/molecules";
|
|
10
11
|
import { useNavigationHandlers, useSettingsScreens } from "./hooks";
|
|
11
12
|
import {
|
|
@@ -41,12 +42,16 @@ export const SettingsStackNavigator: React.FC<SettingsStackNavigatorProps> = (pr
|
|
|
41
42
|
[translations, handlePrivacyPress, handleTermsPress, handleEulaPress]
|
|
42
43
|
);
|
|
43
44
|
|
|
45
|
+
// Get navigation for custom sections
|
|
46
|
+
const navigation = useNavigation();
|
|
47
|
+
|
|
44
48
|
const screens = useSettingsScreens({
|
|
45
49
|
...props,
|
|
46
50
|
aboutConfig,
|
|
47
51
|
legalProps: legalScreenProps,
|
|
48
52
|
notificationTranslations,
|
|
49
53
|
quietHoursTranslations,
|
|
54
|
+
navigation,
|
|
50
55
|
});
|
|
51
56
|
|
|
52
57
|
const navigatorConfig: StackNavigatorConfig<SettingsStackParamList> = {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { useCallback, useMemo } from "react";
|
|
7
7
|
import { Linking } from "react-native";
|
|
8
|
+
import { isDev } from "../../../utils/devUtils";
|
|
8
9
|
import type { AppInfo, LegalUrls } from "../types";
|
|
9
10
|
import type { AboutConfig } from "../../../domains/about";
|
|
10
11
|
|
|
@@ -26,10 +27,14 @@ export const useNavigationHandlers = (
|
|
|
26
27
|
if (canOpen) {
|
|
27
28
|
await Linking.openURL(legalUrls.privacy);
|
|
28
29
|
} else {
|
|
29
|
-
|
|
30
|
+
if (isDev()) {
|
|
31
|
+
console.warn('Cannot open privacy policy URL:', legalUrls.privacy);
|
|
32
|
+
}
|
|
30
33
|
}
|
|
31
34
|
} catch (error) {
|
|
32
|
-
|
|
35
|
+
if (isDev()) {
|
|
36
|
+
console.error('Failed to open privacy policy:', error);
|
|
37
|
+
}
|
|
33
38
|
}
|
|
34
39
|
}, [legalUrls.privacy]);
|
|
35
40
|
|
|
@@ -39,10 +44,14 @@ export const useNavigationHandlers = (
|
|
|
39
44
|
if (canOpen) {
|
|
40
45
|
await Linking.openURL(legalUrls.terms);
|
|
41
46
|
} else {
|
|
42
|
-
|
|
47
|
+
if (isDev()) {
|
|
48
|
+
console.warn('Cannot open terms of service URL:', legalUrls.terms);
|
|
49
|
+
}
|
|
43
50
|
}
|
|
44
51
|
} catch (error) {
|
|
45
|
-
|
|
52
|
+
if (isDev()) {
|
|
53
|
+
console.error('Failed to open terms of service:', error);
|
|
54
|
+
}
|
|
46
55
|
}
|
|
47
56
|
}, [legalUrls.terms]);
|
|
48
57
|
|
|
@@ -53,10 +62,14 @@ export const useNavigationHandlers = (
|
|
|
53
62
|
if (canOpen) {
|
|
54
63
|
await Linking.openURL(legalUrls.eula);
|
|
55
64
|
} else {
|
|
56
|
-
|
|
65
|
+
if (isDev()) {
|
|
66
|
+
console.warn('Cannot open EULA URL:', legalUrls.eula);
|
|
67
|
+
}
|
|
57
68
|
}
|
|
58
69
|
} catch (error) {
|
|
59
|
-
|
|
70
|
+
if (isDev()) {
|
|
71
|
+
console.error('Failed to open EULA:', error);
|
|
72
|
+
}
|
|
60
73
|
}
|
|
61
74
|
}
|
|
62
75
|
}, [legalUrls.eula]);
|
|
@@ -38,6 +38,7 @@ export interface UseSettingsScreensProps extends SettingsStackNavigatorProps {
|
|
|
38
38
|
legalProps: LegalScreenProps;
|
|
39
39
|
notificationTranslations: NotificationSettingsTranslations;
|
|
40
40
|
quietHoursTranslations: QuietHoursTranslations;
|
|
41
|
+
navigation?: any;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export const useSettingsScreens = (props: UseSettingsScreensProps): StackScreen[] => {
|
|
@@ -60,6 +61,7 @@ export const useSettingsScreens = (props: UseSettingsScreensProps): StackScreen[
|
|
|
60
61
|
gamificationConfig,
|
|
61
62
|
videoTutorialConfig,
|
|
62
63
|
accountConfig,
|
|
64
|
+
navigation,
|
|
63
65
|
} = props;
|
|
64
66
|
|
|
65
67
|
const translations = config?.translations;
|
|
@@ -79,12 +81,13 @@ export const useSettingsScreens = (props: UseSettingsScreensProps): StackScreen[
|
|
|
79
81
|
showHeader,
|
|
80
82
|
showCloseButton,
|
|
81
83
|
onClose,
|
|
84
|
+
navigation,
|
|
82
85
|
}
|
|
83
86
|
);
|
|
84
87
|
|
|
85
88
|
const appearanceScreen = {
|
|
86
89
|
name: "Appearance",
|
|
87
|
-
component: AppearanceScreen
|
|
90
|
+
component: AppearanceScreen,
|
|
88
91
|
options: { headerShown: false },
|
|
89
92
|
};
|
|
90
93
|
|
|
@@ -121,7 +124,7 @@ export const useSettingsScreens = (props: UseSettingsScreensProps): StackScreen[
|
|
|
121
124
|
|
|
122
125
|
const additionalStackScreens: StackScreen[] = (additionalScreens || []).map(convertAdditionalScreen);
|
|
123
126
|
|
|
124
|
-
const gamificationScreen = createScreenWithProps("Gamification", GamificationScreen
|
|
127
|
+
const gamificationScreen = createScreenWithProps("Gamification", GamificationScreen, { config: gamificationConfig });
|
|
125
128
|
|
|
126
129
|
const languageScreen = createScreenWithProps("LanguageSelection", LanguageSelectionScreen, {
|
|
127
130
|
headerTitle: featureTranslations?.language?.title || "",
|
|
@@ -130,10 +133,10 @@ export const useSettingsScreens = (props: UseSettingsScreensProps): StackScreen[
|
|
|
130
133
|
|
|
131
134
|
const accountScreen = createConditionalScreen(
|
|
132
135
|
!!accountConfig && !!AccountScreen,
|
|
133
|
-
() => createScreenWithProps("Account", AccountScreen
|
|
136
|
+
() => createScreenWithProps("Account", AccountScreen, { config: accountConfig })
|
|
134
137
|
);
|
|
135
138
|
|
|
136
|
-
const videoTutorialScreen = createScreenWithProps("VideoTutorial", VideoTutorialsScreen
|
|
139
|
+
const videoTutorialScreen = createScreenWithProps("VideoTutorial", VideoTutorialsScreen, {
|
|
137
140
|
...videoTutorialConfig,
|
|
138
141
|
title: videoTutorialConfig?.title || featureTranslations?.videoTutorial?.title || "",
|
|
139
142
|
});
|
|
@@ -198,5 +201,6 @@ export const useSettingsScreens = (props: UseSettingsScreensProps): StackScreen[
|
|
|
198
201
|
gamificationConfig,
|
|
199
202
|
videoTutorialConfig,
|
|
200
203
|
accountConfig,
|
|
204
|
+
navigation,
|
|
201
205
|
]);
|
|
202
206
|
};
|
|
@@ -51,6 +51,8 @@ export interface SettingsScreenProps {
|
|
|
51
51
|
showHeader?: boolean;
|
|
52
52
|
/** Password prompt modal component */
|
|
53
53
|
PasswordPromptComponent?: React.ReactNode;
|
|
54
|
+
/** Navigation object for custom sections */
|
|
55
|
+
navigation?: any;
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
export const SettingsScreen: React.FC<SettingsScreenProps> = (props) => {
|
|
@@ -70,6 +72,7 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = (props) => {
|
|
|
70
72
|
devSettings,
|
|
71
73
|
gamificationConfig,
|
|
72
74
|
PasswordPromptComponent,
|
|
75
|
+
navigation,
|
|
73
76
|
} = props;
|
|
74
77
|
|
|
75
78
|
const {
|
|
@@ -109,6 +112,7 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = (props) => {
|
|
|
109
112
|
customSections={customSections}
|
|
110
113
|
devSettings={devSettings}
|
|
111
114
|
gamificationConfig={gamificationConfig}
|
|
115
|
+
navigation={navigation}
|
|
112
116
|
/>
|
|
113
117
|
)}
|
|
114
118
|
</ScreenLayout>
|
|
@@ -29,6 +29,7 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
|
|
|
29
29
|
emptyStateText,
|
|
30
30
|
devSettings,
|
|
31
31
|
gamificationConfig,
|
|
32
|
+
navigation,
|
|
32
33
|
}) => {
|
|
33
34
|
const translations = normalizedConfig.translations;
|
|
34
35
|
const { level } = useGamification(gamificationConfig);
|
|
@@ -54,7 +55,7 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
|
|
|
54
55
|
/>
|
|
55
56
|
)}
|
|
56
57
|
|
|
57
|
-
<CustomSettingsList customSections={customSections} />
|
|
58
|
+
<CustomSettingsList customSections={customSections} navigation={navigation} />
|
|
58
59
|
|
|
59
60
|
{features.subscription && (normalizedConfig.subscription.config?.route || normalizedConfig.subscription.config?.onPress) && (
|
|
60
61
|
<SettingsSection title={translations?.sections?.subscription || ''}>
|
|
@@ -20,7 +20,7 @@ export function createScreenWithProps<P>(
|
|
|
20
20
|
return {
|
|
21
21
|
name,
|
|
22
22
|
options: { headerShown: false, ...options },
|
|
23
|
-
children: (() => React.createElement(component
|
|
23
|
+
children: (() => React.createElement(component, props)) as () => React.ReactElement,
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -28,11 +28,13 @@ export function createScreenWithProps<P>(
|
|
|
28
28
|
* Convert additional screen to stack screen
|
|
29
29
|
*/
|
|
30
30
|
export function convertAdditionalScreen(screen: AdditionalScreen): StackScreen {
|
|
31
|
-
const stackScreen:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
const stackScreen: StackScreen = {
|
|
32
|
+
name: screen.name,
|
|
33
|
+
component: screen.component,
|
|
34
|
+
children: screen.children,
|
|
35
|
+
options: screen.options,
|
|
36
|
+
};
|
|
37
|
+
return stackScreen;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|