@oxyhq/services 5.13.30 → 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.
@@ -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 initialField changed, reset the flag
251
- if (previousInitialFieldRef.current !== initialField) {
252
- hasSetInitialFieldRef.current = false;
253
- previousInitialFieldRef.current = initialField;
304
+ // If initialSection changed, reset the flag
305
+ if (previousInitialSectionRef.current !== initialSection) {
306
+ hasScrolledToSectionRef.current = false;
307
+ previousInitialSectionRef.current = initialSection;
254
308
  }
255
-
256
- // Set the editing field if initialField is provided and we haven't set it yet
257
- if (initialField && !hasSetInitialFieldRef.current) {
258
- setEditingField(initialField);
259
- hasSetInitialFieldRef.current = true;
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
- }, [initialField]);
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={avatarSectionRef}
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 style={styles.section}>
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 style={styles.section}>
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 style={styles.section}>
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 style={styles.section}>
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>