@oxyhq/services 5.11.9 → 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/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/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/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/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
|
@@ -22,7 +22,16 @@ export interface HeaderProps {
|
|
|
22
22
|
loading?: boolean;
|
|
23
23
|
disabled?: boolean;
|
|
24
24
|
text?: string;
|
|
25
|
+
key?: string;
|
|
25
26
|
};
|
|
27
|
+
rightActions?: Array<{
|
|
28
|
+
icon?: string;
|
|
29
|
+
onPress: () => void;
|
|
30
|
+
loading?: boolean;
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
text?: string;
|
|
33
|
+
key?: string; // optional identifier
|
|
34
|
+
}>;
|
|
26
35
|
theme: 'light' | 'dark';
|
|
27
36
|
showBackButton?: boolean;
|
|
28
37
|
showCloseButton?: boolean;
|
|
@@ -38,6 +47,7 @@ const Header: React.FC<HeaderProps> = ({
|
|
|
38
47
|
onBack,
|
|
39
48
|
onClose,
|
|
40
49
|
rightAction,
|
|
50
|
+
rightActions,
|
|
41
51
|
theme,
|
|
42
52
|
showBackButton = true,
|
|
43
53
|
showCloseButton = false,
|
|
@@ -100,26 +110,24 @@ const Header: React.FC<HeaderProps> = ({
|
|
|
100
110
|
);
|
|
101
111
|
};
|
|
102
112
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const isTextAction = rightAction.text;
|
|
107
|
-
|
|
113
|
+
const renderRightActionButton = (action: NonNullable<HeaderProps['rightAction']>, idx: number) => {
|
|
114
|
+
const isTextAction = action.text;
|
|
108
115
|
return (
|
|
109
116
|
<TouchableOpacity
|
|
117
|
+
key={action.key || idx}
|
|
110
118
|
style={[
|
|
111
119
|
styles.rightActionButton,
|
|
112
120
|
isTextAction ? styles.textActionButton : styles.iconActionButton,
|
|
113
121
|
{
|
|
114
122
|
backgroundColor: isTextAction ? colors.primary : colors.surface,
|
|
115
|
-
opacity:
|
|
123
|
+
opacity: action.disabled ? 0.5 : 1
|
|
116
124
|
}
|
|
117
125
|
]}
|
|
118
|
-
onPress={
|
|
119
|
-
disabled={
|
|
126
|
+
onPress={action.onPress}
|
|
127
|
+
disabled={action.disabled || action.loading}
|
|
120
128
|
activeOpacity={0.7}
|
|
121
129
|
>
|
|
122
|
-
{
|
|
130
|
+
{action.loading ? (
|
|
123
131
|
<View style={styles.loadingContainer}>
|
|
124
132
|
<View style={[styles.loadingDot, { backgroundColor: isTextAction ? '#FFFFFF' : colors.primary }]} />
|
|
125
133
|
<View style={[styles.loadingDot, { backgroundColor: isTextAction ? '#FFFFFF' : colors.primary }]} />
|
|
@@ -127,15 +135,27 @@ const Header: React.FC<HeaderProps> = ({
|
|
|
127
135
|
</View>
|
|
128
136
|
) : isTextAction ? (
|
|
129
137
|
<Text style={[styles.actionText, { color: '#FFFFFF' }]}>
|
|
130
|
-
{
|
|
138
|
+
{action.text}
|
|
131
139
|
</Text>
|
|
132
140
|
) : (
|
|
133
|
-
<Ionicons name={
|
|
141
|
+
<Ionicons name={action.icon as any} size={18} color={colors.primary} />
|
|
134
142
|
)}
|
|
135
143
|
</TouchableOpacity>
|
|
136
144
|
);
|
|
137
145
|
};
|
|
138
146
|
|
|
147
|
+
const renderRightActions = () => {
|
|
148
|
+
if (rightActions && rightActions.length) {
|
|
149
|
+
return (
|
|
150
|
+
<View style={styles.rightActionsRow}>
|
|
151
|
+
{rightActions.map((a, i) => renderRightActionButton(a, i))}
|
|
152
|
+
</View>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (rightAction) return renderRightActionButton(rightAction, 0);
|
|
156
|
+
return null;
|
|
157
|
+
};
|
|
158
|
+
|
|
139
159
|
const renderTitle = () => {
|
|
140
160
|
const titleStyle = variant === 'large' ? styles.titleLarge :
|
|
141
161
|
variant === 'minimal' ? styles.titleMinimal :
|
|
@@ -245,7 +265,7 @@ const Header: React.FC<HeaderProps> = ({
|
|
|
245
265
|
]}>
|
|
246
266
|
{renderBackButton()}
|
|
247
267
|
{renderTitle()}
|
|
248
|
-
{
|
|
268
|
+
{renderRightActions()}
|
|
249
269
|
{renderCloseButton()}
|
|
250
270
|
</View>
|
|
251
271
|
</View>
|
|
@@ -400,6 +420,10 @@ const styles = StyleSheet.create({
|
|
|
400
420
|
borderRadius: 2,
|
|
401
421
|
opacity: 0.6,
|
|
402
422
|
},
|
|
423
|
+
rightActionsRow: {
|
|
424
|
+
flexDirection: 'row',
|
|
425
|
+
alignItems: 'center',
|
|
426
|
+
},
|
|
403
427
|
});
|
|
404
428
|
|
|
405
429
|
export default Header;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import { useCallback, useRef, useState, useEffect, useMemo } from 'react';
|
|
3
|
-
import { View, Text, StyleSheet, Dimensions, Platform, Animated, StatusBar, Keyboard, KeyboardEvent } from 'react-native';
|
|
3
|
+
import { View, Text, StyleSheet, Dimensions, Platform, Animated, StatusBar, Keyboard, KeyboardEvent, AppState } from 'react-native';
|
|
4
4
|
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
5
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
6
6
|
import type { OxyProviderProps } from '../navigation/types';
|
|
@@ -8,6 +8,7 @@ import { OxyContextProvider, useOxy } from '../context/OxyContext';
|
|
|
8
8
|
import OxyRouter from '../navigation/OxyRouter';
|
|
9
9
|
import { FontLoader, setupFonts } from './FontLoader';
|
|
10
10
|
import { Toaster } from '../../lib/sonner';
|
|
11
|
+
import { QueryClient, QueryClientProvider, focusManager } from '@tanstack/react-query';
|
|
11
12
|
|
|
12
13
|
// Import bottom sheet components directly - no longer a peer dependency
|
|
13
14
|
import { BottomSheetModal, BottomSheetBackdrop, type BottomSheetBackdropProps, BottomSheetModalProvider, BottomSheetView, BottomSheetScrollView } from '@gorhom/bottom-sheet';
|
|
@@ -38,47 +39,80 @@ const OxyProvider: React.FC<OxyProviderProps> = (props) => {
|
|
|
38
39
|
// Create internal bottom sheet ref
|
|
39
40
|
const internalBottomSheetRef = useRef<BottomSheetModalRef>(null);
|
|
40
41
|
|
|
42
|
+
// Initialize React Query Client (use provided client or create a default one once)
|
|
43
|
+
const queryClientRef = useRef<QueryClient | null>(null);
|
|
44
|
+
if (!queryClientRef.current) {
|
|
45
|
+
queryClientRef.current = props.queryClient ?? new QueryClient({
|
|
46
|
+
defaultOptions: {
|
|
47
|
+
queries: {
|
|
48
|
+
staleTime: 30_000,
|
|
49
|
+
gcTime: 5 * 60_000,
|
|
50
|
+
retry: 2,
|
|
51
|
+
refetchOnReconnect: true,
|
|
52
|
+
refetchOnWindowFocus: false,
|
|
53
|
+
},
|
|
54
|
+
mutations: {
|
|
55
|
+
retry: 1,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Hook React Query focus manager into React Native AppState
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const subscription = AppState.addEventListener('change', (state) => {
|
|
64
|
+
focusManager.setFocused(state === 'active');
|
|
65
|
+
});
|
|
66
|
+
return () => {
|
|
67
|
+
subscription.remove();
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
41
71
|
// If contextOnly is true, we just provide the context without the bottom sheet UI
|
|
42
72
|
if (contextOnly) {
|
|
43
73
|
return (
|
|
74
|
+
<QueryClientProvider client={queryClientRef.current}>
|
|
75
|
+
<OxyContextProvider
|
|
76
|
+
oxyServices={oxyServices}
|
|
77
|
+
baseURL={baseURL}
|
|
78
|
+
storageKeyPrefix={storageKeyPrefix}
|
|
79
|
+
onAuthStateChange={onAuthStateChange}
|
|
80
|
+
>
|
|
81
|
+
{children}
|
|
82
|
+
</OxyContextProvider>
|
|
83
|
+
</QueryClientProvider>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Otherwise, provide both the context and the bottom sheet UI
|
|
88
|
+
return (
|
|
89
|
+
<QueryClientProvider client={queryClientRef.current}>
|
|
44
90
|
<OxyContextProvider
|
|
45
91
|
oxyServices={oxyServices}
|
|
46
92
|
baseURL={baseURL}
|
|
47
93
|
storageKeyPrefix={storageKeyPrefix}
|
|
48
94
|
onAuthStateChange={onAuthStateChange}
|
|
95
|
+
bottomSheetRef={internalBottomSheetRef}
|
|
49
96
|
>
|
|
50
|
-
|
|
97
|
+
<FontLoader>
|
|
98
|
+
<GestureHandlerRootView style={styles.gestureHandlerRoot}>
|
|
99
|
+
<BottomSheetModalProvider>
|
|
100
|
+
<StatusBar translucent backgroundColor="transparent" />
|
|
101
|
+
<SafeAreaProvider>
|
|
102
|
+
<OxyBottomSheet {...bottomSheetProps} bottomSheetRef={internalBottomSheetRef} oxyServices={oxyServices} />
|
|
103
|
+
{children}
|
|
104
|
+
</SafeAreaProvider>
|
|
105
|
+
</BottomSheetModalProvider>
|
|
106
|
+
{/* Global Toaster for app-wide notifications outside of Modal contexts - only show if internal toaster is disabled */}
|
|
107
|
+
{!showInternalToaster && (
|
|
108
|
+
<View style={styles.toasterContainer}>
|
|
109
|
+
<Toaster position="top-center" swipeToDismissDirection="left" offset={15} />
|
|
110
|
+
</View>
|
|
111
|
+
)}
|
|
112
|
+
</GestureHandlerRootView>
|
|
113
|
+
</FontLoader>
|
|
51
114
|
</OxyContextProvider>
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Otherwise, provide both the context and the bottom sheet UI
|
|
56
|
-
return (
|
|
57
|
-
<OxyContextProvider
|
|
58
|
-
oxyServices={oxyServices}
|
|
59
|
-
baseURL={baseURL}
|
|
60
|
-
storageKeyPrefix={storageKeyPrefix}
|
|
61
|
-
onAuthStateChange={onAuthStateChange}
|
|
62
|
-
bottomSheetRef={internalBottomSheetRef}
|
|
63
|
-
>
|
|
64
|
-
<FontLoader>
|
|
65
|
-
<GestureHandlerRootView style={styles.gestureHandlerRoot}>
|
|
66
|
-
<BottomSheetModalProvider>
|
|
67
|
-
<StatusBar translucent backgroundColor="transparent" />
|
|
68
|
-
<SafeAreaProvider>
|
|
69
|
-
<OxyBottomSheet {...bottomSheetProps} bottomSheetRef={internalBottomSheetRef} oxyServices={oxyServices} />
|
|
70
|
-
{children}
|
|
71
|
-
</SafeAreaProvider>
|
|
72
|
-
</BottomSheetModalProvider>
|
|
73
|
-
{/* Global Toaster for app-wide notifications outside of Modal contexts - only show if internal toaster is disabled */}
|
|
74
|
-
{!showInternalToaster && (
|
|
75
|
-
<View style={styles.toasterContainer}>
|
|
76
|
-
<Toaster position="top-center" swipeToDismissDirection="left" offset={15} />
|
|
77
|
-
</View>
|
|
78
|
-
)}
|
|
79
|
-
</GestureHandlerRootView>
|
|
80
|
-
</FontLoader>
|
|
81
|
-
</OxyContextProvider>
|
|
115
|
+
</QueryClientProvider>
|
|
82
116
|
);
|
|
83
117
|
};
|
|
84
118
|
|
|
@@ -2,18 +2,15 @@ import type React from 'react';
|
|
|
2
2
|
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
|
3
3
|
import { Ionicons } from '@expo/vector-icons';
|
|
4
4
|
import Avatar from './Avatar';
|
|
5
|
+
import { useOxy } from '../context/OxyContext';
|
|
5
6
|
import { fontFamilies } from '../styles/fonts';
|
|
6
7
|
|
|
7
8
|
interface ProfileCardProps {
|
|
8
9
|
user: {
|
|
9
10
|
username: string;
|
|
10
11
|
email?: string;
|
|
11
|
-
name?: {
|
|
12
|
-
|
|
13
|
-
};
|
|
14
|
-
avatar?: {
|
|
15
|
-
url?: string;
|
|
16
|
-
};
|
|
12
|
+
name?: { full?: string };
|
|
13
|
+
avatar?: string; // file id
|
|
17
14
|
};
|
|
18
15
|
theme: 'light' | 'dark';
|
|
19
16
|
onEditPress?: () => void;
|
|
@@ -29,6 +26,7 @@ const ProfileCard: React.FC<ProfileCardProps> = ({
|
|
|
29
26
|
showCloseButton = false,
|
|
30
27
|
}) => {
|
|
31
28
|
const isDarkTheme = theme === 'dark';
|
|
29
|
+
const { oxyServices } = useOxy();
|
|
32
30
|
const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
|
|
33
31
|
const secondaryBackgroundColor = isDarkTheme ? '#222222' : '#FFFFFF';
|
|
34
32
|
const primaryColor = '#0066CC';
|
|
@@ -43,7 +41,7 @@ const ProfileCard: React.FC<ProfileCardProps> = ({
|
|
|
43
41
|
]}>
|
|
44
42
|
<View style={styles.userProfile}>
|
|
45
43
|
<Avatar
|
|
46
|
-
uri={user?.avatar
|
|
44
|
+
uri={user?.avatar ? oxyServices.getFileDownloadUrl(user.avatar as string, 'thumb') : undefined}
|
|
47
45
|
name={user?.name?.full || user?.username}
|
|
48
46
|
size={60}
|
|
49
47
|
theme={theme}
|
|
@@ -56,7 +54,7 @@ const ProfileCard: React.FC<ProfileCardProps> = ({
|
|
|
56
54
|
</Text>
|
|
57
55
|
)}
|
|
58
56
|
{onEditPress && (
|
|
59
|
-
<TouchableOpacity
|
|
57
|
+
<TouchableOpacity
|
|
60
58
|
style={styles.editProfileButton}
|
|
61
59
|
onPress={onEditPress}
|
|
62
60
|
>
|
|
@@ -12,6 +12,5 @@ export { default as Avatar } from './Avatar';
|
|
|
12
12
|
export { default as FollowButton } from './FollowButton';
|
|
13
13
|
export { FontLoader, setupFonts } from './FontLoader';
|
|
14
14
|
export { default as OxyLogo } from './OxyLogo';
|
|
15
|
-
export { default as OxyProvider } from './OxyProvider';
|
|
16
15
|
export { default as OxySignInButton } from './OxySignInButton';
|
|
17
16
|
export { default as OxyPayButton } from './OxyPayButton';
|
|
@@ -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);
|