@oxyhq/services 5.11.8 → 5.11.10
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/core/OxyServices.js +104 -10
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/ui/components/AnimationExample.js +213 -0
- package/lib/commonjs/ui/components/AnimationExample.js.map +1 -0
- package/lib/commonjs/ui/components/FollowButton.js +58 -47
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/GroupedItem.js +2 -1
- package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
- package/lib/commonjs/ui/components/GroupedSection.js +3 -0
- package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
- package/lib/commonjs/ui/components/Header.js +25 -11
- package/lib/commonjs/ui/components/Header.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +69 -33
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileCard.js +5 -1
- package/lib/commonjs/ui/components/ProfileCard.js.map +1 -1
- package/lib/commonjs/ui/components/index.js +0 -7
- package/lib/commonjs/ui/components/index.js.map +1 -1
- package/lib/commonjs/ui/components/internal/TextField.js +8 -4
- package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +161 -0
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +97 -38
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useFollow.types.js +2 -0
- package/lib/commonjs/ui/hooks/useFollow.types.js.map +1 -0
- package/lib/commonjs/ui/navigation/OxyRouter.js +10 -0
- package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountCenterScreen.js +26 -14
- package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js +3 -3
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +64 -15
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +4 -4
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FeedbackScreen.js +72 -75
- package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +286 -126
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +322 -0
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/ProfileScreen.js +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SessionManagementScreen.js +176 -174
- package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +43 -52
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignUpScreen.js +6 -4
- package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +386 -0
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +25 -15
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +16 -9
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/styles/authStyles.js +1 -1
- package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
- package/lib/module/core/OxyServices.js +103 -9
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/ui/components/AnimationExample.js +209 -0
- package/lib/module/ui/components/AnimationExample.js.map +1 -0
- package/lib/module/ui/components/FollowButton.js +58 -47
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/GroupedItem.js +2 -1
- package/lib/module/ui/components/GroupedItem.js.map +1 -1
- package/lib/module/ui/components/GroupedSection.js +3 -0
- package/lib/module/ui/components/GroupedSection.js.map +1 -1
- package/lib/module/ui/components/Header.js +25 -11
- package/lib/module/ui/components/Header.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +70 -34
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/ProfileCard.js +5 -1
- package/lib/module/ui/components/ProfileCard.js.map +1 -1
- package/lib/module/ui/components/index.js +0 -1
- package/lib/module/ui/components/index.js.map +1 -1
- package/lib/module/ui/components/internal/TextField.js +8 -4
- package/lib/module/ui/components/internal/TextField.js.map +1 -1
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +156 -0
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +97 -39
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useFollow.types.js +2 -0
- package/lib/module/ui/hooks/useFollow.types.js.map +1 -0
- package/lib/module/ui/navigation/OxyRouter.js +10 -0
- package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/module/ui/screens/AccountCenterScreen.js +12 -1
- package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountOverviewScreen.js +3 -3
- package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +64 -15
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +4 -4
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/FeedbackScreen.js +72 -75
- package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +285 -125
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/LanguageSelectorScreen.js +319 -0
- package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -0
- package/lib/module/ui/screens/ProfileScreen.js +1 -1
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/SessionManagementScreen.js +177 -175
- package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +44 -53
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/SignUpScreen.js +6 -4
- package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +382 -0
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -0
- package/lib/module/ui/screens/internal/SignInPasswordStep.js +23 -14
- package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/internal/SignInUsernameStep.js +15 -9
- package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/module/ui/styles/authStyles.js +1 -1
- package/lib/module/ui/styles/authStyles.js.map +1 -1
- package/lib/typescript/core/OxyServices.d.ts +95 -4
- package/lib/typescript/core/OxyServices.d.ts.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +1 -5
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/models/session.d.ts +1 -4
- package/lib/typescript/models/session.d.ts.map +1 -1
- package/lib/typescript/ui/components/AnimationExample.d.ts +4 -0
- package/lib/typescript/ui/components/AnimationExample.d.ts.map +1 -0
- package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
- package/lib/typescript/ui/components/Header.d.ts +9 -0
- package/lib/typescript/ui/components/Header.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/components/ProfileCard.d.ts +1 -3
- package/lib/typescript/ui/components/ProfileCard.d.ts.map +1 -1
- package/lib/typescript/ui/components/index.d.ts +0 -1
- package/lib/typescript/ui/components/index.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
- package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts +27 -0
- package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -0
- package/lib/typescript/ui/context/OxyContext.d.ts +6 -2
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useFollow.types.d.ts +33 -0
- package/lib/typescript/ui/hooks/useFollow.types.d.ts.map +1 -0
- package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
- package/lib/typescript/ui/navigation/types.d.ts +5 -0
- package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/FileManagementScreen.d.ts +18 -1
- package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +7 -0
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts +13 -0
- package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +5 -5
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +4 -4
- package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +1 -1
- package/lib/typescript/ui/styles/authStyles.d.ts +1 -1
- package/package.json +10 -2
- package/src/core/OxyServices.ts +107 -13
- package/src/models/interfaces.ts +2 -5
- package/src/models/session.ts +1 -4
- package/src/ui/components/AnimationExample.tsx +194 -0
- package/src/ui/components/FollowButton.tsx +65 -45
- package/src/ui/components/GroupedItem.tsx +1 -0
- package/src/ui/components/GroupedSection.tsx +1 -1
- package/src/ui/components/Header.tsx +36 -12
- package/src/ui/components/OxyProvider.tsx +66 -32
- package/src/ui/components/ProfileCard.tsx +6 -8
- package/src/ui/components/index.ts +0 -1
- package/src/ui/components/internal/TextField.tsx +12 -6
- package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +158 -0
- package/src/ui/context/OxyContext.tsx +84 -54
- package/src/ui/hooks/useFollow.types.ts +33 -0
- package/src/ui/navigation/OxyRouter.tsx +10 -0
- package/src/ui/navigation/types.ts +6 -0
- package/src/ui/screens/AccountCenterScreen.tsx +13 -7
- package/src/ui/screens/AccountOverviewScreen.tsx +3 -3
- package/src/ui/screens/AccountSettingsScreen.tsx +65 -13
- package/src/ui/screens/AccountSwitcherScreen.tsx +4 -4
- package/src/ui/screens/FeedbackScreen.tsx +57 -80
- package/src/ui/screens/FileManagementScreen.tsx +278 -175
- package/src/ui/screens/LanguageSelectorScreen.tsx +322 -0
- package/src/ui/screens/ProfileScreen.tsx +6 -1
- package/src/ui/screens/SessionManagementScreen.tsx +148 -151
- package/src/ui/screens/SignInScreen.tsx +43 -62
- package/src/ui/screens/SignUpScreen.tsx +3 -5
- package/src/ui/screens/WelcomeNewUserScreen.tsx +272 -0
- package/src/ui/screens/internal/SignInPasswordStep.tsx +28 -13
- package/src/ui/screens/internal/SignInUsernameStep.tsx +21 -11
- package/src/ui/screens/karma/KarmaCenterScreen.tsx +1 -1
- package/src/ui/styles/authStyles.ts +1 -1
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
type NativeSyntheticEvent,
|
|
17
17
|
type TargetedEvent,
|
|
18
18
|
type TextInputFocusEventData,
|
|
19
|
+
type FocusEvent,
|
|
20
|
+
type BlurEvent,
|
|
19
21
|
} from 'react-native';
|
|
20
22
|
import { Ionicons } from '@expo/vector-icons';
|
|
21
23
|
import Svg, { Path } from 'react-native-svg';
|
|
@@ -304,16 +306,20 @@ const TextField = forwardRef<TextInput, TextFieldProps>(({
|
|
|
304
306
|
}, [formatValue, inputMask, customMask]);
|
|
305
307
|
|
|
306
308
|
// Handle focus
|
|
307
|
-
const handleFocus = useCallback((event:
|
|
309
|
+
const handleFocus = useCallback((event: FocusEvent) => {
|
|
308
310
|
if (disabled) return;
|
|
309
311
|
setFocused(true);
|
|
310
|
-
|
|
312
|
+
// Convert FocusEvent to the expected type for parent callback
|
|
313
|
+
const syntheticEvent = event as any;
|
|
314
|
+
onFocus?.(syntheticEvent);
|
|
311
315
|
}, [disabled, onFocus]);
|
|
312
316
|
|
|
313
317
|
// Handle blur
|
|
314
|
-
const handleBlur = useCallback((event:
|
|
318
|
+
const handleBlur = useCallback((event: BlurEvent) => {
|
|
315
319
|
setFocused(false);
|
|
316
|
-
|
|
320
|
+
// Convert BlurEvent to the expected type for parent callback
|
|
321
|
+
const syntheticEvent = event as any;
|
|
322
|
+
onBlur?.(syntheticEvent);
|
|
317
323
|
}, [onBlur]);
|
|
318
324
|
|
|
319
325
|
// Handle mouse events
|
|
@@ -421,7 +427,7 @@ const TextField = forwardRef<TextInput, TextFieldProps>(({
|
|
|
421
427
|
position: 'relative',
|
|
422
428
|
...Platform.select({
|
|
423
429
|
web: {
|
|
424
|
-
outlineStyle:
|
|
430
|
+
outlineStyle: undefined,
|
|
425
431
|
outlineWidth: 0,
|
|
426
432
|
outlineOffset: 0,
|
|
427
433
|
},
|
|
@@ -441,7 +447,7 @@ const TextField = forwardRef<TextInput, TextFieldProps>(({
|
|
|
441
447
|
...Platform.select({
|
|
442
448
|
web: {
|
|
443
449
|
border: 'none',
|
|
444
|
-
outlineStyle:
|
|
450
|
+
outlineStyle: undefined,
|
|
445
451
|
outlineWidth: 0,
|
|
446
452
|
outlineOffset: 0,
|
|
447
453
|
boxShadow: 'none',
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
|
2
|
+
import { View, Text, LayoutChangeEvent } from 'react-native';
|
|
3
|
+
import type { FileMetadata } from '../../../models/interfaces';
|
|
4
|
+
// Using plain React Native styles (nativewind not installed in this repo)
|
|
5
|
+
|
|
6
|
+
export interface JustifiedPhotoGridProps {
|
|
7
|
+
photos: FileMetadata[];
|
|
8
|
+
photoDimensions: { [key: string]: { width: number; height: number } };
|
|
9
|
+
loadPhotoDimensions: (photos: FileMetadata[]) => Promise<void>;
|
|
10
|
+
createJustifiedRows: (photos: FileMetadata[], containerWidth: number) => FileMetadata[][];
|
|
11
|
+
renderJustifiedPhotoItem: (photo: FileMetadata, width: number, height: number, isLast: boolean) => React.ReactElement;
|
|
12
|
+
renderSimplePhotoItem: (photo: FileMetadata, index: number) => React.ReactElement;
|
|
13
|
+
textColor: string;
|
|
14
|
+
/**
|
|
15
|
+
* Full available width from parent. If omitted, component will measure itself and adapt responsively.
|
|
16
|
+
*/
|
|
17
|
+
containerWidth?: number; // optional; will auto-measure if not provided
|
|
18
|
+
gap?: number;
|
|
19
|
+
minRowHeight?: number;
|
|
20
|
+
maxRowHeight?: number;
|
|
21
|
+
dateFormatLocale?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Responsive justified photo grid that stretches to the provided containerWidth.
|
|
26
|
+
* Uses flex rows with proportional children widths instead of absolute pixel widths so it always fills.
|
|
27
|
+
*/
|
|
28
|
+
const JustifiedPhotoGrid: React.FC<JustifiedPhotoGridProps> = ({
|
|
29
|
+
photos,
|
|
30
|
+
photoDimensions,
|
|
31
|
+
loadPhotoDimensions,
|
|
32
|
+
createJustifiedRows,
|
|
33
|
+
renderJustifiedPhotoItem,
|
|
34
|
+
textColor,
|
|
35
|
+
containerWidth: explicitWidth,
|
|
36
|
+
gap = 4,
|
|
37
|
+
minRowHeight = 100,
|
|
38
|
+
maxRowHeight = 300,
|
|
39
|
+
dateFormatLocale = 'en-US',
|
|
40
|
+
}) => {
|
|
41
|
+
// Responsive width measurement if not explicitly provided
|
|
42
|
+
const [measuredWidth, setMeasuredWidth] = useState<number | null>(null);
|
|
43
|
+
const effectiveWidth = explicitWidth ?? measuredWidth ?? 0; // 0 until measured
|
|
44
|
+
|
|
45
|
+
const onLayoutContainer = useCallback((e: LayoutChangeEvent) => {
|
|
46
|
+
if (explicitWidth) return; // ignore if controlled
|
|
47
|
+
const w = e.nativeEvent.layout.width;
|
|
48
|
+
setMeasuredWidth(prev => (prev === w ? prev : w));
|
|
49
|
+
}, [explicitWidth]);
|
|
50
|
+
// Ensure dimensions are loaded for displayed photos
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
loadPhotoDimensions(photos);
|
|
53
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
54
|
+
}, [photos.map(p => p.id).join(',')]);
|
|
55
|
+
|
|
56
|
+
// Group photos by date first
|
|
57
|
+
const photosByDate = useMemo(() => {
|
|
58
|
+
return photos.reduce((groups: { [key: string]: FileMetadata[] }, photo) => {
|
|
59
|
+
const date = new Date(photo.uploadDate).toDateString();
|
|
60
|
+
if (!groups[date]) groups[date] = [];
|
|
61
|
+
groups[date].push(photo);
|
|
62
|
+
return groups;
|
|
63
|
+
}, {} as { [key: string]: FileMetadata[] });
|
|
64
|
+
}, [photos]);
|
|
65
|
+
|
|
66
|
+
const sortedDates = useMemo(() => Object.keys(photosByDate).sort((a, b) => new Date(b).getTime() - new Date(a).getTime()), [photosByDate]);
|
|
67
|
+
|
|
68
|
+
// Track measured width of each date section (may differ if parent applies horizontal padding/margins)
|
|
69
|
+
const [dateWidths, setDateWidths] = useState<Record<string, number>>({});
|
|
70
|
+
const onLayoutDate = useCallback((date: string, width: number) => {
|
|
71
|
+
setDateWidths(prev => (prev[date] === width ? prev : { ...prev, [date]: width }));
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<View style={{ width: '100%' }} onLayout={onLayoutContainer}>
|
|
76
|
+
{/* If width not yet known (uncontrolled), avoid rendering to prevent layout jump */}
|
|
77
|
+
{effectiveWidth === 0 && !explicitWidth ? null : (
|
|
78
|
+
<>
|
|
79
|
+
{sortedDates.map((date: string) => {
|
|
80
|
+
const dayPhotos = photosByDate[date];
|
|
81
|
+
// createJustifiedRows should build rows such that the "ideal" height (availableWidth / totalAspect) stays within min/max.
|
|
82
|
+
// We pass the effective container width.
|
|
83
|
+
const dateWidth = dateWidths[date] ?? effectiveWidth; // fallback to overall width until measured
|
|
84
|
+
const rows = dateWidth > 0 ? createJustifiedRows(dayPhotos, dateWidth) : [];
|
|
85
|
+
return (
|
|
86
|
+
<View
|
|
87
|
+
key={date}
|
|
88
|
+
style={{ marginBottom: 24, width: '100%' }}
|
|
89
|
+
onLayout={e => onLayoutDate(date, e.nativeEvent.layout.width)}
|
|
90
|
+
>
|
|
91
|
+
<Text style={{ fontSize: 14, fontWeight: '600', marginBottom: 12, color: textColor }}>
|
|
92
|
+
{new Date(date).toLocaleDateString(dateFormatLocale, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}
|
|
93
|
+
</Text>
|
|
94
|
+
<View style={{ width: '100%' }}>
|
|
95
|
+
{rows.map((row, rowIndex) => {
|
|
96
|
+
// Compute total aspect ratios using loaded dimensions (fallback 4/3)
|
|
97
|
+
const aspects = row.map(p => {
|
|
98
|
+
const dims = photoDimensions[p.id];
|
|
99
|
+
return dims ? dims.width / dims.height : 4 / 3;
|
|
100
|
+
});
|
|
101
|
+
const totalAspect = aspects.reduce((a, b) => a + b, 0);
|
|
102
|
+
const gapsTotal = gap * (row.length - 1);
|
|
103
|
+
const availableWidth = dateWidth - gapsTotal;
|
|
104
|
+
// Ideal height that perfectly fills width when preserving aspect ratios
|
|
105
|
+
const idealHeight = availableWidth / totalAspect;
|
|
106
|
+
// We rely on row construction keeping idealHeight within min/max bounds; if not, clamp but then distribute leftover/overflow.
|
|
107
|
+
let rowHeight = idealHeight;
|
|
108
|
+
let widthAdjustment = 0; // difference to distribute if clamped
|
|
109
|
+
if (idealHeight < minRowHeight) {
|
|
110
|
+
rowHeight = minRowHeight;
|
|
111
|
+
widthAdjustment = availableWidth - rowHeight * totalAspect; // negative means overflow
|
|
112
|
+
} else if (idealHeight > maxRowHeight) {
|
|
113
|
+
rowHeight = maxRowHeight;
|
|
114
|
+
widthAdjustment = availableWidth - rowHeight * totalAspect;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Pre-compute widths maintaining aspect ratios
|
|
118
|
+
let widths = aspects.map(ar => ar * rowHeight);
|
|
119
|
+
// If we have widthAdjustment (due to clamping) distribute proportionally so row still fills exactly
|
|
120
|
+
if (widthAdjustment !== 0) {
|
|
121
|
+
const widthSum = widths.reduce((a, b) => a + b, 0);
|
|
122
|
+
widths = widths.map(w => w + (w / widthSum) * widthAdjustment);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// To combat rounding issues, adjust last item width to fill precisely
|
|
126
|
+
const widthSumRounded = widths.reduce((a, b) => a + b, 0);
|
|
127
|
+
const roundingDiff = availableWidth - widthSumRounded;
|
|
128
|
+
if (Math.abs(roundingDiff) > 0.5) {
|
|
129
|
+
widths[widths.length - 1] += roundingDiff; // minimal correction
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<View key={rowIndex} style={{ flexDirection: 'row', width: '100%', marginBottom: 4 }}>
|
|
134
|
+
{row.map((p, i) => {
|
|
135
|
+
const photoWidth = widths[i];
|
|
136
|
+
return (
|
|
137
|
+
<View
|
|
138
|
+
key={p.id}
|
|
139
|
+
style={{ width: photoWidth, height: rowHeight, marginRight: i === row.length - 1 ? 0 : gap }}
|
|
140
|
+
>
|
|
141
|
+
{renderJustifiedPhotoItem(p, photoWidth, rowHeight, i === row.length - 1)}
|
|
142
|
+
</View>
|
|
143
|
+
);
|
|
144
|
+
})}
|
|
145
|
+
</View>
|
|
146
|
+
);
|
|
147
|
+
})}
|
|
148
|
+
</View>
|
|
149
|
+
</View>
|
|
150
|
+
);
|
|
151
|
+
})}
|
|
152
|
+
</>
|
|
153
|
+
)}
|
|
154
|
+
</View>
|
|
155
|
+
);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export default React.memo(JustifiedPhotoGrid);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { createContext, useContext, useEffect, useCallback, type ReactNode, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import type { UseFollowHook } from '../hooks/useFollow.types';
|
|
2
3
|
import { View, Text } from 'react-native';
|
|
3
4
|
import { OxyServices } from '../../core';
|
|
4
5
|
import type { User, ApiError } from '../../models/interfaces';
|
|
@@ -9,8 +10,8 @@ import { toast } from '../../lib/sonner';
|
|
|
9
10
|
import { useAuthStore } from '../stores/authStore';
|
|
10
11
|
|
|
11
12
|
// Define the context shape
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
// NOTE: We intentionally avoid importing useFollow here to prevent a require cycle.
|
|
14
|
+
// If consumers relied on `const { useFollow } = useOxy()`, we provide a lazy proxy below.
|
|
14
15
|
|
|
15
16
|
export interface OxyContextState {
|
|
16
17
|
// Authentication state
|
|
@@ -22,6 +23,9 @@ export interface OxyContextState {
|
|
|
22
23
|
isLoading: boolean;
|
|
23
24
|
error: string | null;
|
|
24
25
|
|
|
26
|
+
// Language state
|
|
27
|
+
currentLanguage: string;
|
|
28
|
+
|
|
25
29
|
// Auth methods
|
|
26
30
|
login: (username: string, password: string, deviceName?: string) => Promise<User>;
|
|
27
31
|
logout: (targetSessionId?: string) => Promise<void>;
|
|
@@ -33,6 +37,9 @@ export interface OxyContextState {
|
|
|
33
37
|
removeSession: (sessionId: string) => Promise<void>;
|
|
34
38
|
refreshSessions: () => Promise<void>;
|
|
35
39
|
|
|
40
|
+
// Language methods
|
|
41
|
+
setLanguage: (languageId: string) => Promise<void>;
|
|
42
|
+
|
|
36
43
|
// Device management methods
|
|
37
44
|
getDeviceSessions: () => Promise<any[]>;
|
|
38
45
|
logoutAllDeviceSessions: () => Promise<void>;
|
|
@@ -47,9 +54,10 @@ export interface OxyContextState {
|
|
|
47
54
|
hideBottomSheet?: () => void;
|
|
48
55
|
|
|
49
56
|
/**
|
|
50
|
-
* useFollow hook
|
|
57
|
+
* (Deprecated) useFollow hook access via context. Prefer: import { useFollow } from '@oxyhq/services';
|
|
58
|
+
* Kept for backward compatibility; implemented as a lazy dynamic require to avoid circular dependency.
|
|
51
59
|
*/
|
|
52
|
-
useFollow:
|
|
60
|
+
useFollow: UseFollowHook; // Back-compat; prefer direct import
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
// Create the context with default values
|
|
@@ -122,6 +130,7 @@ const getStorage = async (): Promise<StorageInterface> => {
|
|
|
122
130
|
// Storage keys for sessions
|
|
123
131
|
const getStorageKeys = (prefix = 'oxy_session') => ({
|
|
124
132
|
activeSessionId: `${prefix}_active_session_id`, // Only store the active session ID
|
|
133
|
+
language: `${prefix}_language`, // Store the selected language
|
|
125
134
|
});
|
|
126
135
|
|
|
127
136
|
export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
@@ -162,6 +171,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
162
171
|
const [sessions, setSessions] = React.useState<ClientSession[]>([]);
|
|
163
172
|
const [activeSessionId, setActiveSessionId] = React.useState<string | null>(null);
|
|
164
173
|
const [storage, setStorage] = React.useState<StorageInterface | null>(null);
|
|
174
|
+
const [currentLanguage, setCurrentLanguage] = React.useState<string>('en');
|
|
165
175
|
// Add a new state to track token restoration
|
|
166
176
|
const [tokenReady, setTokenReady] = React.useState(false);
|
|
167
177
|
|
|
@@ -185,88 +195,66 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
185
195
|
const platformStorage = await getStorage();
|
|
186
196
|
setStorage(platformStorage);
|
|
187
197
|
} catch (error) {
|
|
188
|
-
console.error('
|
|
198
|
+
console.error('Init storage failed', error);
|
|
189
199
|
useAuthStore.setState({ error: 'Failed to initialize storage' });
|
|
190
200
|
}
|
|
191
201
|
};
|
|
192
|
-
|
|
193
202
|
initStorage();
|
|
194
203
|
}, []);
|
|
195
204
|
|
|
196
|
-
//
|
|
205
|
+
// Initialize authentication state
|
|
197
206
|
useEffect(() => {
|
|
198
207
|
const initAuth = async () => {
|
|
199
208
|
if (!storage) return;
|
|
200
|
-
|
|
201
209
|
useAuthStore.setState({ isLoading: true });
|
|
202
210
|
try {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
211
|
+
setTokenReady(false);
|
|
212
|
+
|
|
213
|
+
// Load saved language preference
|
|
214
|
+
const savedLanguage = await storage.getItem(keys.language);
|
|
215
|
+
if (savedLanguage) {
|
|
216
|
+
setCurrentLanguage(savedLanguage);
|
|
217
|
+
}
|
|
209
218
|
|
|
219
|
+
// Try to restore active session from storage
|
|
220
|
+
const storedActiveSessionId = await storage.getItem(keys.activeSessionId);
|
|
210
221
|
if (storedActiveSessionId) {
|
|
211
|
-
// Validate the stored session with the backend
|
|
212
222
|
try {
|
|
213
|
-
const validation = await oxyServices.validateSession(storedActiveSessionId, {
|
|
214
|
-
useHeaderValidation: true
|
|
215
|
-
});
|
|
216
|
-
|
|
223
|
+
const validation = await oxyServices.validateSession(storedActiveSessionId, { useHeaderValidation: true });
|
|
217
224
|
if (validation.valid) {
|
|
218
|
-
console.log('Auth - session validated successfully');
|
|
219
225
|
setActiveSessionId(storedActiveSessionId);
|
|
220
|
-
|
|
221
|
-
// Get access token for API calls
|
|
222
226
|
await oxyServices.getTokenBySession(storedActiveSessionId);
|
|
223
|
-
|
|
224
|
-
// Load full user data from backend
|
|
225
227
|
const fullUser = await oxyServices.getUserBySession(storedActiveSessionId);
|
|
226
228
|
loginSuccess(fullUser);
|
|
227
|
-
setMinimalUser({
|
|
228
|
-
id: fullUser.id,
|
|
229
|
-
username: fullUser.username,
|
|
230
|
-
avatar: fullUser.avatar
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// Load sessions from backend
|
|
229
|
+
setMinimalUser({ id: fullUser.id, username: fullUser.username, avatar: fullUser.avatar });
|
|
234
230
|
const serverSessions = await oxyServices.getSessionsBySessionId(storedActiveSessionId);
|
|
235
|
-
const clientSessions: ClientSession[] = serverSessions.map(
|
|
236
|
-
sessionId:
|
|
237
|
-
deviceId:
|
|
238
|
-
expiresAt:
|
|
239
|
-
lastActive:
|
|
240
|
-
userId:
|
|
231
|
+
const clientSessions: ClientSession[] = serverSessions.map(s => ({
|
|
232
|
+
sessionId: s.sessionId,
|
|
233
|
+
deviceId: s.deviceId,
|
|
234
|
+
expiresAt: s.expiresAt || new Date().toISOString(),
|
|
235
|
+
lastActive: s.lastActive || new Date().toISOString(),
|
|
236
|
+
userId: s.userId || fullUser.id
|
|
241
237
|
}));
|
|
242
238
|
setSessions(clientSessions);
|
|
243
|
-
|
|
244
|
-
if (onAuthStateChange) {
|
|
245
|
-
onAuthStateChange(fullUser);
|
|
246
|
-
}
|
|
239
|
+
onAuthStateChange?.(fullUser);
|
|
247
240
|
} else {
|
|
248
|
-
console.log('Auth - session invalid, clearing storage');
|
|
249
241
|
await clearAllStorage();
|
|
250
242
|
}
|
|
251
|
-
} catch (
|
|
252
|
-
console.error('
|
|
243
|
+
} catch (e) {
|
|
244
|
+
console.error('Session validation error', e);
|
|
253
245
|
await clearAllStorage();
|
|
254
246
|
}
|
|
255
|
-
} else {
|
|
256
|
-
console.log('Auth - no stored session found, user needs to login');
|
|
257
247
|
}
|
|
258
|
-
|
|
259
|
-
|
|
248
|
+
setTokenReady(true);
|
|
249
|
+
} catch (e) {
|
|
250
|
+
console.error('Auth init error', e);
|
|
260
251
|
await clearAllStorage();
|
|
261
252
|
} finally {
|
|
262
253
|
useAuthStore.setState({ isLoading: false });
|
|
263
254
|
}
|
|
264
255
|
};
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
initAuth();
|
|
268
|
-
}
|
|
269
|
-
}, [storage, oxyServices, keys, onAuthStateChange, loginSuccess, setMinimalUser, clearAllStorage]);
|
|
256
|
+
initAuth();
|
|
257
|
+
}, [storage, oxyServices, keys, onAuthStateChange, loginSuccess, clearAllStorage]);
|
|
270
258
|
|
|
271
259
|
|
|
272
260
|
|
|
@@ -630,6 +618,26 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
630
618
|
}
|
|
631
619
|
}, [activeSessionId, oxyServices]);
|
|
632
620
|
|
|
621
|
+
// Language management method
|
|
622
|
+
const setLanguage = useCallback(async (languageId: string): Promise<void> => {
|
|
623
|
+
if (!storage) throw new Error('Storage not initialized');
|
|
624
|
+
|
|
625
|
+
try {
|
|
626
|
+
// Save language preference
|
|
627
|
+
await storage.setItem(keys.language, languageId);
|
|
628
|
+
setCurrentLanguage(languageId);
|
|
629
|
+
|
|
630
|
+
console.log(`Language changed to ${languageId}`);
|
|
631
|
+
|
|
632
|
+
// TODO: Here you can add any additional logic needed for app-wide language updates
|
|
633
|
+
// such as updating i18n configuration, refreshing translations, etc.
|
|
634
|
+
|
|
635
|
+
} catch (error) {
|
|
636
|
+
console.error('Error saving language preference:', error);
|
|
637
|
+
throw error;
|
|
638
|
+
}
|
|
639
|
+
}, [storage, keys.language]);
|
|
640
|
+
|
|
633
641
|
// Bottom sheet control methods
|
|
634
642
|
const showBottomSheet = useCallback((screenOrConfig?: string | { screen: string; props?: Record<string, any> }) => {
|
|
635
643
|
console.log('showBottomSheet called with:', screenOrConfig);
|
|
@@ -691,6 +699,24 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
691
699
|
});
|
|
692
700
|
|
|
693
701
|
// Context value - optimized to prevent unnecessary re-renders
|
|
702
|
+
// Lazy proxy to load the hook only when accessed, breaking the static import cycle.
|
|
703
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
|
|
704
|
+
const useFollowProxy: UseFollowHook = (userId?: string | string[]) => {
|
|
705
|
+
try {
|
|
706
|
+
// Dynamically require to avoid top-level cycle
|
|
707
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
708
|
+
const mod = require('../hooks/useFollow');
|
|
709
|
+
if (mod && typeof mod.useFollow === 'function') {
|
|
710
|
+
return mod.useFollow(userId);
|
|
711
|
+
}
|
|
712
|
+
console.warn('useFollow module did not export a function as expected');
|
|
713
|
+
return { isFollowing: false, isLoading: false, error: null, toggleFollow: async () => { }, setFollowStatus: () => { }, fetchStatus: async () => { }, clearError: () => { }, followerCount: null, followingCount: null, isLoadingCounts: false, fetchUserCounts: async () => { }, setFollowerCount: () => { }, setFollowingCount: () => { } } as any;
|
|
714
|
+
} catch (e) {
|
|
715
|
+
console.warn('Failed to dynamically load useFollow hook:', e);
|
|
716
|
+
return { isFollowing: false, isLoading: false, error: null, toggleFollow: async () => { }, setFollowStatus: () => { }, fetchStatus: async () => { }, clearError: () => { }, followerCount: null, followingCount: null, isLoadingCounts: false, fetchUserCounts: async () => { }, setFollowerCount: () => { }, setFollowingCount: () => { } } as any;
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
|
|
694
720
|
const contextValue: OxyContextState = useMemo(() => ({
|
|
695
721
|
user,
|
|
696
722
|
minimalUser,
|
|
@@ -699,6 +725,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
699
725
|
isAuthenticated,
|
|
700
726
|
isLoading,
|
|
701
727
|
error,
|
|
728
|
+
currentLanguage,
|
|
702
729
|
login,
|
|
703
730
|
logout,
|
|
704
731
|
logoutAll,
|
|
@@ -706,6 +733,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
706
733
|
switchSession,
|
|
707
734
|
removeSession,
|
|
708
735
|
refreshSessions,
|
|
736
|
+
setLanguage,
|
|
709
737
|
getDeviceSessions,
|
|
710
738
|
logoutAllDeviceSessions,
|
|
711
739
|
updateDeviceName,
|
|
@@ -713,7 +741,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
713
741
|
bottomSheetRef,
|
|
714
742
|
showBottomSheet,
|
|
715
743
|
hideBottomSheet,
|
|
716
|
-
useFollow:
|
|
744
|
+
useFollow: useFollowProxy,
|
|
717
745
|
}), [
|
|
718
746
|
user?.id, // Only depend on user ID, not the entire user object
|
|
719
747
|
minimalUser?.id,
|
|
@@ -722,6 +750,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
722
750
|
isAuthenticated,
|
|
723
751
|
isLoading,
|
|
724
752
|
error,
|
|
753
|
+
currentLanguage,
|
|
725
754
|
login,
|
|
726
755
|
logout,
|
|
727
756
|
logoutAll,
|
|
@@ -729,6 +758,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
729
758
|
switchSession,
|
|
730
759
|
removeSession,
|
|
731
760
|
refreshSessions,
|
|
761
|
+
setLanguage,
|
|
732
762
|
getDeviceSessions,
|
|
733
763
|
logoutAllDeviceSessions,
|
|
734
764
|
updateDeviceName,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Type-only definition for the useFollow hook to allow context exposure without runtime import cycles.
|
|
2
|
+
// Expand this as needed to better reflect the real return type.
|
|
3
|
+
|
|
4
|
+
export type SingleFollowResult = {
|
|
5
|
+
isFollowing: boolean;
|
|
6
|
+
isLoading: boolean;
|
|
7
|
+
error: string | null;
|
|
8
|
+
toggleFollow: () => Promise<void>;
|
|
9
|
+
setFollowStatus: (following: boolean) => void;
|
|
10
|
+
fetchStatus: () => Promise<void>;
|
|
11
|
+
clearError: () => void;
|
|
12
|
+
followerCount: number | null;
|
|
13
|
+
followingCount: number | null;
|
|
14
|
+
isLoadingCounts: boolean;
|
|
15
|
+
fetchUserCounts: () => Promise<void>;
|
|
16
|
+
setFollowerCount: (count: number) => void;
|
|
17
|
+
setFollowingCount: (count: number) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type MultiFollowResult = {
|
|
21
|
+
followData: Record<string, { isFollowing: boolean; isLoading: boolean; error: string | null }>;
|
|
22
|
+
toggleFollowForUser: (userId: string) => Promise<void>;
|
|
23
|
+
setFollowStatusForUser: (userId: string, following: boolean) => void;
|
|
24
|
+
fetchStatusForUser: (userId: string) => Promise<void>;
|
|
25
|
+
fetchAllStatuses: () => Promise<void>;
|
|
26
|
+
clearErrorForUser: (userId: string) => void;
|
|
27
|
+
isAnyLoading: boolean;
|
|
28
|
+
hasAnyError: boolean;
|
|
29
|
+
allFollowing: boolean;
|
|
30
|
+
allNotFollowing: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type UseFollowHook = (userId?: string | string[]) => SingleFollowResult | MultiFollowResult;
|
|
@@ -25,6 +25,8 @@ import UserLinksScreen from '../screens/UserLinksScreen';
|
|
|
25
25
|
import FileManagementScreen from '../screens/FileManagementScreen';
|
|
26
26
|
import RecoverAccountScreen from '../screens/RecoverAccountScreen';
|
|
27
27
|
import PaymentGatewayScreen from '../screens/PaymentGatewayScreen';
|
|
28
|
+
import WelcomeNewUserScreen from '../screens/WelcomeNewUserScreen';
|
|
29
|
+
import LanguageSelectorScreen from '../screens/LanguageSelectorScreen';
|
|
28
30
|
|
|
29
31
|
// Import types
|
|
30
32
|
import type { OxyRouterProps, RouteConfig } from './types';
|
|
@@ -115,6 +117,14 @@ const routes: Record<string, RouteConfig> = {
|
|
|
115
117
|
component: PaymentGatewayScreen,
|
|
116
118
|
snapPoints: ['60%', '90%'],
|
|
117
119
|
},
|
|
120
|
+
WelcomeNewUser: {
|
|
121
|
+
component: WelcomeNewUserScreen,
|
|
122
|
+
snapPoints: ['65%', '90%'],
|
|
123
|
+
},
|
|
124
|
+
LanguageSelector: {
|
|
125
|
+
component: LanguageSelectorScreen,
|
|
126
|
+
snapPoints: ['70%', '100%'],
|
|
127
|
+
},
|
|
118
128
|
};
|
|
119
129
|
|
|
120
130
|
const OxyRouter: React.FC<OxyRouterProps> = ({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OxyServices } from '../../core';
|
|
2
2
|
import type { User } from '../../models/interfaces';
|
|
3
3
|
import type { ComponentType, ReactNode } from 'react';
|
|
4
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Base props for all screens in the Oxy UI system
|
|
@@ -138,4 +139,9 @@ export interface OxyProviderProps {
|
|
|
138
139
|
* @default true
|
|
139
140
|
*/
|
|
140
141
|
showInternalToaster?: boolean;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Optional QueryClient instance for React Query. If not provided, a sensible default is created.
|
|
145
|
+
*/
|
|
146
|
+
queryClient?: QueryClient;
|
|
141
147
|
}
|
|
@@ -16,13 +16,11 @@ import { toast } from '../../lib/sonner';
|
|
|
16
16
|
import { confirmAction } from '../utils/confirmAction';
|
|
17
17
|
import { Ionicons } from '@expo/vector-icons';
|
|
18
18
|
import { fontFamilies } from '../styles/fonts';
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
GroupedItem
|
|
25
|
-
} from '../components';
|
|
19
|
+
import ProfileCard from '../components/ProfileCard';
|
|
20
|
+
import Section from '../components/Section';
|
|
21
|
+
import QuickActions from '../components/QuickActions';
|
|
22
|
+
import GroupedSection from '../components/GroupedSection';
|
|
23
|
+
import GroupedItem from '../components/GroupedItem';
|
|
26
24
|
|
|
27
25
|
const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
28
26
|
onClose,
|
|
@@ -218,6 +216,14 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
218
216
|
subtitle: 'Manage notification settings',
|
|
219
217
|
onPress: () => toast.info('Notifications feature coming soon!'),
|
|
220
218
|
}] : []),
|
|
219
|
+
{
|
|
220
|
+
id: 'language',
|
|
221
|
+
icon: 'language',
|
|
222
|
+
iconColor: '#32D74B',
|
|
223
|
+
title: 'Language',
|
|
224
|
+
subtitle: 'Choose your preferred language',
|
|
225
|
+
onPress: () => navigate('LanguageSelector'),
|
|
226
|
+
},
|
|
221
227
|
{
|
|
222
228
|
id: 'help',
|
|
223
229
|
icon: 'help-circle',
|
|
@@ -192,7 +192,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
|
192
192
|
<>
|
|
193
193
|
<View style={styles.userIcon}>
|
|
194
194
|
<Avatar
|
|
195
|
-
uri={user?.avatar
|
|
195
|
+
uri={user?.avatar ? oxyServices.getFileDownloadUrl(user.avatar as string, 'thumb') : undefined}
|
|
196
196
|
name={user?.name?.full}
|
|
197
197
|
size={40}
|
|
198
198
|
theme={theme}
|
|
@@ -301,8 +301,8 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
|
301
301
|
customContent: (
|
|
302
302
|
<>
|
|
303
303
|
<View style={styles.userIcon}>
|
|
304
|
-
{account.avatar
|
|
305
|
-
<Image source={{ uri: account.avatar
|
|
304
|
+
{account.avatar ? (
|
|
305
|
+
<Image source={{ uri: oxyServices.getFileStreamUrl(account.avatar as string) }} style={styles.accountAvatarImage} />
|
|
306
306
|
) : (
|
|
307
307
|
<View style={styles.accountAvatarFallback}>
|
|
308
308
|
<Text style={styles.accountAvatarText}>
|