@oxyhq/services 5.13.29 → 5.13.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.
- package/lib/commonjs/ui/context/OxyContext.js +62 -4
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionSocket.js +66 -17
- package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +150 -13
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +62 -4
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useSessionSocket.js +66 -17
- package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +150 -13
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useSessionSocket.d.ts +3 -1
- package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts +1 -0
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ui/context/OxyContext.tsx +71 -3
- package/src/ui/hooks/useSessionSocket.ts +66 -17
- package/src/ui/screens/AccountSettingsScreen.tsx +158 -18
|
@@ -8,18 +8,22 @@ interface UseSessionSocketProps {
|
|
|
8
8
|
currentDeviceId: string | null | undefined;
|
|
9
9
|
refreshSessions: () => Promise<void>;
|
|
10
10
|
logout: () => Promise<void>;
|
|
11
|
+
clearSessionState: () => Promise<void>;
|
|
11
12
|
baseURL: string;
|
|
12
13
|
onRemoteSignOut?: () => void;
|
|
14
|
+
onSessionRemoved?: (sessionId: string) => void;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
export function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSessions, logout, baseURL, onRemoteSignOut }: UseSessionSocketProps) {
|
|
17
|
+
export function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSessions, logout, clearSessionState, baseURL, onRemoteSignOut, onSessionRemoved }: UseSessionSocketProps) {
|
|
16
18
|
const socketRef = useRef<any>(null);
|
|
17
19
|
const joinedRoomRef = useRef<string | null>(null);
|
|
18
20
|
|
|
19
21
|
// Store callbacks in refs to avoid re-joining when they change
|
|
20
22
|
const refreshSessionsRef = useRef(refreshSessions);
|
|
21
23
|
const logoutRef = useRef(logout);
|
|
24
|
+
const clearSessionStateRef = useRef(clearSessionState);
|
|
22
25
|
const onRemoteSignOutRef = useRef(onRemoteSignOut);
|
|
26
|
+
const onSessionRemovedRef = useRef(onSessionRemoved);
|
|
23
27
|
const activeSessionIdRef = useRef(activeSessionId);
|
|
24
28
|
const currentDeviceIdRef = useRef(currentDeviceId);
|
|
25
29
|
|
|
@@ -27,10 +31,12 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
|
|
|
27
31
|
useEffect(() => {
|
|
28
32
|
refreshSessionsRef.current = refreshSessions;
|
|
29
33
|
logoutRef.current = logout;
|
|
34
|
+
clearSessionStateRef.current = clearSessionState;
|
|
30
35
|
onRemoteSignOutRef.current = onRemoteSignOut;
|
|
36
|
+
onSessionRemovedRef.current = onSessionRemoved;
|
|
31
37
|
activeSessionIdRef.current = activeSessionId;
|
|
32
38
|
currentDeviceIdRef.current = currentDeviceId;
|
|
33
|
-
}, [refreshSessions, logout, onRemoteSignOut, activeSessionId, currentDeviceId]);
|
|
39
|
+
}, [refreshSessions, logout, clearSessionState, onRemoteSignOut, onSessionRemoved, activeSessionId, currentDeviceId]);
|
|
34
40
|
|
|
35
41
|
useEffect(() => {
|
|
36
42
|
if (!userId || !baseURL) {
|
|
@@ -89,47 +95,89 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
|
|
|
89
95
|
|
|
90
96
|
// Handle different event types
|
|
91
97
|
if (data.type === 'session_removed') {
|
|
92
|
-
//
|
|
98
|
+
// Track removed session
|
|
99
|
+
if (data.sessionId && onSessionRemovedRef.current) {
|
|
100
|
+
onSessionRemovedRef.current(data.sessionId);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// If the removed sessionId matches the current activeSessionId, immediately clear state
|
|
93
104
|
if (data.sessionId === currentActiveSessionId) {
|
|
94
105
|
if (onRemoteSignOutRef.current) {
|
|
95
106
|
onRemoteSignOutRef.current();
|
|
96
107
|
} else {
|
|
97
108
|
toast.info('You have been signed out remotely.');
|
|
98
109
|
}
|
|
99
|
-
|
|
110
|
+
// Use clearSessionState since session was already removed server-side
|
|
111
|
+
clearSessionStateRef.current();
|
|
100
112
|
} else {
|
|
101
|
-
// Otherwise, just refresh the sessions list
|
|
102
|
-
refreshSessionsRef.current()
|
|
113
|
+
// Otherwise, just refresh the sessions list (with error handling)
|
|
114
|
+
refreshSessionsRef.current().catch((error) => {
|
|
115
|
+
// Silently handle errors from refresh - they're expected if sessions were removed
|
|
116
|
+
if (__DEV__) {
|
|
117
|
+
console.debug('Failed to refresh sessions after session_removed:', error);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
103
120
|
}
|
|
104
121
|
} else if (data.type === 'device_removed') {
|
|
105
|
-
//
|
|
122
|
+
// Track all removed sessions from this device
|
|
123
|
+
if (data.sessionIds && onSessionRemovedRef.current) {
|
|
124
|
+
for (const sessionId of data.sessionIds) {
|
|
125
|
+
onSessionRemovedRef.current(sessionId);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// If the removed deviceId matches the current device, immediately clear state
|
|
106
130
|
if (data.deviceId && data.deviceId === currentDeviceId) {
|
|
107
131
|
if (onRemoteSignOutRef.current) {
|
|
108
132
|
onRemoteSignOutRef.current();
|
|
109
133
|
} else {
|
|
110
134
|
toast.info('This device has been removed. You have been signed out.');
|
|
111
135
|
}
|
|
112
|
-
|
|
136
|
+
// Use clearSessionState since sessions were already removed server-side
|
|
137
|
+
clearSessionStateRef.current();
|
|
113
138
|
} else {
|
|
114
|
-
// Otherwise, refresh sessions and device list
|
|
115
|
-
refreshSessionsRef.current()
|
|
139
|
+
// Otherwise, refresh sessions and device list (with error handling)
|
|
140
|
+
refreshSessionsRef.current().catch((error) => {
|
|
141
|
+
// Silently handle errors from refresh - they're expected if sessions were removed
|
|
142
|
+
if (__DEV__) {
|
|
143
|
+
console.debug('Failed to refresh sessions after device_removed:', error);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
116
146
|
}
|
|
117
147
|
} else if (data.type === 'sessions_removed') {
|
|
118
|
-
//
|
|
148
|
+
// Track all removed sessions
|
|
149
|
+
if (data.sessionIds && onSessionRemovedRef.current) {
|
|
150
|
+
for (const sessionId of data.sessionIds) {
|
|
151
|
+
onSessionRemovedRef.current(sessionId);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// If the current activeSessionId is in the removed sessionIds list, immediately clear state
|
|
119
156
|
if (data.sessionIds && currentActiveSessionId && data.sessionIds.includes(currentActiveSessionId)) {
|
|
120
157
|
if (onRemoteSignOutRef.current) {
|
|
121
158
|
onRemoteSignOutRef.current();
|
|
122
159
|
} else {
|
|
123
160
|
toast.info('You have been signed out remotely.');
|
|
124
161
|
}
|
|
125
|
-
|
|
162
|
+
// Use clearSessionState since sessions were already removed server-side
|
|
163
|
+
clearSessionStateRef.current();
|
|
126
164
|
} else {
|
|
127
|
-
// Otherwise, refresh sessions list
|
|
128
|
-
refreshSessionsRef.current()
|
|
165
|
+
// Otherwise, refresh sessions list (with error handling)
|
|
166
|
+
refreshSessionsRef.current().catch((error) => {
|
|
167
|
+
// Silently handle errors from refresh - they're expected if sessions were removed
|
|
168
|
+
if (__DEV__) {
|
|
169
|
+
console.debug('Failed to refresh sessions after sessions_removed:', error);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
129
172
|
}
|
|
130
173
|
} else {
|
|
131
|
-
// For other event types (e.g., session_created), refresh sessions
|
|
132
|
-
refreshSessionsRef.current()
|
|
174
|
+
// For other event types (e.g., session_created), refresh sessions (with error handling)
|
|
175
|
+
refreshSessionsRef.current().catch((error) => {
|
|
176
|
+
// Log but don't throw - refresh errors shouldn't break the socket handler
|
|
177
|
+
if (__DEV__) {
|
|
178
|
+
console.debug('Failed to refresh sessions after session_update:', error);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
133
181
|
|
|
134
182
|
// If the current session was logged out (legacy behavior), handle it specially
|
|
135
183
|
if (data.sessionId === currentActiveSessionId) {
|
|
@@ -138,7 +186,8 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
|
|
|
138
186
|
} else {
|
|
139
187
|
toast.info('You have been signed out remotely.');
|
|
140
188
|
}
|
|
141
|
-
|
|
189
|
+
// Use clearSessionState since session was already removed server-side
|
|
190
|
+
clearSessionStateRef.current();
|
|
142
191
|
}
|
|
143
192
|
}
|
|
144
193
|
};
|
|
@@ -33,12 +33,13 @@ const locationSearchCache = new TTLCache<any[]>(60 * 60 * 1000); // 1 hour cache
|
|
|
33
33
|
registerCacheForCleanup(linkMetadataCache);
|
|
34
34
|
registerCacheForCleanup(locationSearchCache);
|
|
35
35
|
|
|
36
|
-
const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string }> = ({
|
|
36
|
+
const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string; initialSection?: string }> = ({
|
|
37
37
|
onClose,
|
|
38
38
|
theme,
|
|
39
39
|
goBack,
|
|
40
40
|
navigate,
|
|
41
41
|
initialField,
|
|
42
|
+
initialSection,
|
|
42
43
|
}) => {
|
|
43
44
|
const { user: userFromContext, oxyServices, isLoading: authLoading, isAuthenticated, showBottomSheet, activeSessionId } = useOxy();
|
|
44
45
|
const { t } = useI18n();
|
|
@@ -52,6 +53,20 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string
|
|
|
52
53
|
const scrollViewRef = useRef<ScrollView>(null);
|
|
53
54
|
const avatarSectionRef = useRef<View>(null);
|
|
54
55
|
const [avatarSectionY, setAvatarSectionY] = useState<number | null>(null);
|
|
56
|
+
|
|
57
|
+
// Section refs for navigation
|
|
58
|
+
const profilePictureSectionRef = useRef<View>(null);
|
|
59
|
+
const basicInfoSectionRef = useRef<View>(null);
|
|
60
|
+
const aboutSectionRef = useRef<View>(null);
|
|
61
|
+
const quickActionsSectionRef = useRef<View>(null);
|
|
62
|
+
const securitySectionRef = useRef<View>(null);
|
|
63
|
+
|
|
64
|
+
// Section Y positions for scrolling
|
|
65
|
+
const [profilePictureSectionY, setProfilePictureSectionY] = useState<number | null>(null);
|
|
66
|
+
const [basicInfoSectionY, setBasicInfoSectionY] = useState<number | null>(null);
|
|
67
|
+
const [aboutSectionY, setAboutSectionY] = useState<number | null>(null);
|
|
68
|
+
const [quickActionsSectionY, setQuickActionsSectionY] = useState<number | null>(null);
|
|
69
|
+
const [securitySectionY, setSecuritySectionY] = useState<number | null>(null);
|
|
55
70
|
|
|
56
71
|
// Two-Factor (TOTP) state
|
|
57
72
|
const [totpSetupUrl, setTotpSetupUrl] = useState<string | null>(null);
|
|
@@ -246,19 +261,69 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string
|
|
|
246
261
|
// Use a ref to track if we've already set the initial field to avoid loops
|
|
247
262
|
const hasSetInitialFieldRef = useRef(false);
|
|
248
263
|
const previousInitialFieldRef = useRef<string | undefined>(undefined);
|
|
264
|
+
const initialFieldTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
265
|
+
|
|
266
|
+
// Delay constant for scroll completion
|
|
267
|
+
const SCROLL_DELAY_MS = 600;
|
|
268
|
+
|
|
269
|
+
// Helper to get current value for a field
|
|
270
|
+
const getFieldCurrentValue = useCallback((field: string): string => {
|
|
271
|
+
switch (field) {
|
|
272
|
+
case 'displayName':
|
|
273
|
+
return displayName;
|
|
274
|
+
case 'username':
|
|
275
|
+
return username;
|
|
276
|
+
case 'email':
|
|
277
|
+
return email;
|
|
278
|
+
case 'bio':
|
|
279
|
+
return bio;
|
|
280
|
+
case 'location':
|
|
281
|
+
case 'links':
|
|
282
|
+
case 'twoFactor':
|
|
283
|
+
return '';
|
|
284
|
+
default:
|
|
285
|
+
return '';
|
|
286
|
+
}
|
|
287
|
+
}, [displayName, username, email, bio]);
|
|
288
|
+
|
|
289
|
+
// Handle initialSection prop to scroll to specific section
|
|
290
|
+
const hasScrolledToSectionRef = useRef(false);
|
|
291
|
+
const previousInitialSectionRef = useRef<string | undefined>(undefined);
|
|
292
|
+
const SCROLL_OFFSET = 100; // Offset to show section near top of viewport
|
|
293
|
+
|
|
294
|
+
// Map section names to their Y positions
|
|
295
|
+
const sectionYPositions = useMemo(() => ({
|
|
296
|
+
profilePicture: profilePictureSectionY,
|
|
297
|
+
basicInfo: basicInfoSectionY,
|
|
298
|
+
about: aboutSectionY,
|
|
299
|
+
quickActions: quickActionsSectionY,
|
|
300
|
+
security: securitySectionY,
|
|
301
|
+
}), [profilePictureSectionY, basicInfoSectionY, aboutSectionY, quickActionsSectionY, securitySectionY]);
|
|
302
|
+
|
|
249
303
|
useEffect(() => {
|
|
250
|
-
// If
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
304
|
+
// If initialSection changed, reset the flag
|
|
305
|
+
if (previousInitialSectionRef.current !== initialSection) {
|
|
306
|
+
hasScrolledToSectionRef.current = false;
|
|
307
|
+
previousInitialSectionRef.current = initialSection;
|
|
254
308
|
}
|
|
255
|
-
|
|
256
|
-
//
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
|
|
309
|
+
|
|
310
|
+
// Scroll to the specified section if initialSection is provided and we haven't scrolled yet
|
|
311
|
+
if (initialSection && !hasScrolledToSectionRef.current) {
|
|
312
|
+
const sectionY = sectionYPositions[initialSection as keyof typeof sectionYPositions];
|
|
313
|
+
|
|
314
|
+
if (sectionY !== null && sectionY !== undefined && scrollViewRef.current) {
|
|
315
|
+
requestAnimationFrame(() => {
|
|
316
|
+
requestAnimationFrame(() => {
|
|
317
|
+
scrollViewRef.current?.scrollTo({
|
|
318
|
+
y: Math.max(0, sectionY - SCROLL_OFFSET),
|
|
319
|
+
animated: true,
|
|
320
|
+
});
|
|
321
|
+
hasScrolledToSectionRef.current = true;
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
}
|
|
260
325
|
}
|
|
261
|
-
}, [
|
|
326
|
+
}, [initialSection, sectionYPositions]);
|
|
262
327
|
|
|
263
328
|
const handleSave = async () => {
|
|
264
329
|
if (!user) return;
|
|
@@ -400,7 +465,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string
|
|
|
400
465
|
});
|
|
401
466
|
}, [showBottomSheet, oxyServices, avatarFileId, updateUser, user]);
|
|
402
467
|
|
|
403
|
-
const startEditing = (type: string, currentValue: string) => {
|
|
468
|
+
const startEditing = useCallback((type: string, currentValue: string) => {
|
|
404
469
|
switch (type) {
|
|
405
470
|
case 'displayName':
|
|
406
471
|
setTempDisplayName(displayName);
|
|
@@ -429,7 +494,50 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string
|
|
|
429
494
|
break;
|
|
430
495
|
}
|
|
431
496
|
setEditingField(type);
|
|
432
|
-
};
|
|
497
|
+
}, [displayName, lastName]);
|
|
498
|
+
|
|
499
|
+
// Handle initialField prop - must be after startEditing and openAvatarPicker are declared
|
|
500
|
+
useEffect(() => {
|
|
501
|
+
// Clear any pending timeout
|
|
502
|
+
if (initialFieldTimeoutRef.current) {
|
|
503
|
+
clearTimeout(initialFieldTimeoutRef.current);
|
|
504
|
+
initialFieldTimeoutRef.current = null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// If initialField changed, reset the flag
|
|
508
|
+
if (previousInitialFieldRef.current !== initialField) {
|
|
509
|
+
hasSetInitialFieldRef.current = false;
|
|
510
|
+
previousInitialFieldRef.current = initialField;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Set the editing field if initialField is provided and we haven't set it yet
|
|
514
|
+
if (initialField && !hasSetInitialFieldRef.current) {
|
|
515
|
+
// Special handling for avatar - open avatar picker directly
|
|
516
|
+
if (initialField === 'avatar') {
|
|
517
|
+
// Wait for section to be scrolled, then open picker
|
|
518
|
+
initialFieldTimeoutRef.current = setTimeout(() => {
|
|
519
|
+
openAvatarPicker();
|
|
520
|
+
hasSetInitialFieldRef.current = true;
|
|
521
|
+
}, SCROLL_DELAY_MS);
|
|
522
|
+
} else {
|
|
523
|
+
// For other fields, get current value and start editing after scroll
|
|
524
|
+
const currentValue = getFieldCurrentValue(initialField);
|
|
525
|
+
|
|
526
|
+
// Wait for section to be scrolled, then start editing
|
|
527
|
+
initialFieldTimeoutRef.current = setTimeout(() => {
|
|
528
|
+
startEditing(initialField, currentValue);
|
|
529
|
+
hasSetInitialFieldRef.current = true;
|
|
530
|
+
}, SCROLL_DELAY_MS);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return () => {
|
|
535
|
+
if (initialFieldTimeoutRef.current) {
|
|
536
|
+
clearTimeout(initialFieldTimeoutRef.current);
|
|
537
|
+
initialFieldTimeoutRef.current = null;
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
}, [initialField, getFieldCurrentValue, startEditing, openAvatarPicker]);
|
|
433
541
|
|
|
434
542
|
const saveField = (type: string) => {
|
|
435
543
|
animateSaveButton(0.95); // Scale down slightly for animation
|
|
@@ -1247,11 +1355,15 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string
|
|
|
1247
1355
|
)}
|
|
1248
1356
|
{/* Profile Picture Section */}
|
|
1249
1357
|
<View
|
|
1250
|
-
ref={
|
|
1358
|
+
ref={(ref) => {
|
|
1359
|
+
avatarSectionRef.current = ref;
|
|
1360
|
+
profilePictureSectionRef.current = ref;
|
|
1361
|
+
}}
|
|
1251
1362
|
style={styles.section}
|
|
1252
1363
|
onLayout={(event) => {
|
|
1253
1364
|
const { y } = event.nativeEvent.layout;
|
|
1254
1365
|
setAvatarSectionY(y);
|
|
1366
|
+
setProfilePictureSectionY(y);
|
|
1255
1367
|
}}
|
|
1256
1368
|
>
|
|
1257
1369
|
<Text style={[styles.sectionTitle, { color: themeStyles.isDarkTheme ? '#8E8E93' : '#8E8E93' }]}>
|
|
@@ -1319,7 +1431,14 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string
|
|
|
1319
1431
|
</View>
|
|
1320
1432
|
|
|
1321
1433
|
{/* Basic Information */}
|
|
1322
|
-
<View
|
|
1434
|
+
<View
|
|
1435
|
+
ref={basicInfoSectionRef}
|
|
1436
|
+
style={styles.section}
|
|
1437
|
+
onLayout={(event) => {
|
|
1438
|
+
const { y } = event.nativeEvent.layout;
|
|
1439
|
+
setBasicInfoSectionY(y);
|
|
1440
|
+
}}
|
|
1441
|
+
>
|
|
1323
1442
|
<Text style={[styles.sectionTitle, { color: themeStyles.isDarkTheme ? '#8E8E93' : '#8E8E93' }]}>
|
|
1324
1443
|
{t('editProfile.sections.basicInfo') || 'BASIC INFORMATION'}
|
|
1325
1444
|
</Text>
|
|
@@ -1357,7 +1476,14 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string
|
|
|
1357
1476
|
</View>
|
|
1358
1477
|
|
|
1359
1478
|
{/* About You */}
|
|
1360
|
-
<View
|
|
1479
|
+
<View
|
|
1480
|
+
ref={aboutSectionRef}
|
|
1481
|
+
style={styles.section}
|
|
1482
|
+
onLayout={(event) => {
|
|
1483
|
+
const { y } = event.nativeEvent.layout;
|
|
1484
|
+
setAboutSectionY(y);
|
|
1485
|
+
}}
|
|
1486
|
+
>
|
|
1361
1487
|
<Text style={[styles.sectionTitle, { color: themeStyles.isDarkTheme ? '#8E8E93' : '#8E8E93' }]}>
|
|
1362
1488
|
{t('editProfile.sections.about') || 'ABOUT YOU'}
|
|
1363
1489
|
</Text>
|
|
@@ -1457,7 +1583,14 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string
|
|
|
1457
1583
|
</View>
|
|
1458
1584
|
|
|
1459
1585
|
{/* Quick Actions */}
|
|
1460
|
-
<View
|
|
1586
|
+
<View
|
|
1587
|
+
ref={quickActionsSectionRef}
|
|
1588
|
+
style={styles.section}
|
|
1589
|
+
onLayout={(event) => {
|
|
1590
|
+
const { y } = event.nativeEvent.layout;
|
|
1591
|
+
setQuickActionsSectionY(y);
|
|
1592
|
+
}}
|
|
1593
|
+
>
|
|
1461
1594
|
<Text style={[styles.sectionTitle, { color: themeStyles.isDarkTheme ? '#8E8E93' : '#8E8E93' }]}>
|
|
1462
1595
|
{t('editProfile.sections.quickActions') || 'QUICK ACTIONS'}
|
|
1463
1596
|
</Text>
|
|
@@ -1495,7 +1628,14 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string
|
|
|
1495
1628
|
</View>
|
|
1496
1629
|
|
|
1497
1630
|
{/* Security */}
|
|
1498
|
-
<View
|
|
1631
|
+
<View
|
|
1632
|
+
ref={securitySectionRef}
|
|
1633
|
+
style={styles.section}
|
|
1634
|
+
onLayout={(event) => {
|
|
1635
|
+
const { y } = event.nativeEvent.layout;
|
|
1636
|
+
setSecuritySectionY(y);
|
|
1637
|
+
}}
|
|
1638
|
+
>
|
|
1499
1639
|
<Text style={[styles.sectionTitle, { color: themeStyles.isDarkTheme ? '#8E8E93' : '#8E8E93' }]}>
|
|
1500
1640
|
{t('editProfile.sections.security') || 'SECURITY'}
|
|
1501
1641
|
</Text>
|