@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.
Files changed (192) hide show
  1. package/lib/commonjs/ui/components/AnimationExample.js +213 -0
  2. package/lib/commonjs/ui/components/AnimationExample.js.map +1 -0
  3. package/lib/commonjs/ui/components/FollowButton.js +58 -47
  4. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  5. package/lib/commonjs/ui/components/GroupedItem.js +2 -1
  6. package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
  7. package/lib/commonjs/ui/components/GroupedSection.js +3 -0
  8. package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
  9. package/lib/commonjs/ui/components/Header.js +25 -11
  10. package/lib/commonjs/ui/components/Header.js.map +1 -1
  11. package/lib/commonjs/ui/components/OxyProvider.js +69 -33
  12. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  13. package/lib/commonjs/ui/components/ProfileCard.js +5 -1
  14. package/lib/commonjs/ui/components/ProfileCard.js.map +1 -1
  15. package/lib/commonjs/ui/components/index.js +0 -7
  16. package/lib/commonjs/ui/components/index.js.map +1 -1
  17. package/lib/commonjs/ui/components/internal/TextField.js +8 -4
  18. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
  19. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +161 -0
  20. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
  21. package/lib/commonjs/ui/context/OxyContext.js +97 -38
  22. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  23. package/lib/commonjs/ui/hooks/useFollow.types.js +2 -0
  24. package/lib/commonjs/ui/hooks/useFollow.types.js.map +1 -0
  25. package/lib/commonjs/ui/navigation/OxyRouter.js +10 -0
  26. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  27. package/lib/commonjs/ui/screens/AccountCenterScreen.js +26 -14
  28. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  29. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +3 -3
  30. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  31. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +64 -15
  32. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  33. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +4 -4
  34. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  35. package/lib/commonjs/ui/screens/FeedbackScreen.js +72 -75
  36. package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
  37. package/lib/commonjs/ui/screens/FileManagementScreen.js +286 -126
  38. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  39. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +322 -0
  40. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -0
  41. package/lib/commonjs/ui/screens/ProfileScreen.js +1 -1
  42. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  43. package/lib/commonjs/ui/screens/SessionManagementScreen.js +176 -174
  44. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  45. package/lib/commonjs/ui/screens/SignInScreen.js +43 -52
  46. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  47. package/lib/commonjs/ui/screens/SignUpScreen.js +6 -4
  48. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  49. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +386 -0
  50. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -0
  51. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +25 -15
  52. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  53. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +16 -9
  54. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  55. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -1
  56. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  57. package/lib/commonjs/ui/styles/authStyles.js +1 -1
  58. package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
  59. package/lib/module/ui/components/AnimationExample.js +209 -0
  60. package/lib/module/ui/components/AnimationExample.js.map +1 -0
  61. package/lib/module/ui/components/FollowButton.js +58 -47
  62. package/lib/module/ui/components/FollowButton.js.map +1 -1
  63. package/lib/module/ui/components/GroupedItem.js +2 -1
  64. package/lib/module/ui/components/GroupedItem.js.map +1 -1
  65. package/lib/module/ui/components/GroupedSection.js +3 -0
  66. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  67. package/lib/module/ui/components/Header.js +25 -11
  68. package/lib/module/ui/components/Header.js.map +1 -1
  69. package/lib/module/ui/components/OxyProvider.js +70 -34
  70. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  71. package/lib/module/ui/components/ProfileCard.js +5 -1
  72. package/lib/module/ui/components/ProfileCard.js.map +1 -1
  73. package/lib/module/ui/components/index.js +0 -1
  74. package/lib/module/ui/components/index.js.map +1 -1
  75. package/lib/module/ui/components/internal/TextField.js +8 -4
  76. package/lib/module/ui/components/internal/TextField.js.map +1 -1
  77. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +156 -0
  78. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
  79. package/lib/module/ui/context/OxyContext.js +97 -39
  80. package/lib/module/ui/context/OxyContext.js.map +1 -1
  81. package/lib/module/ui/hooks/useFollow.types.js +2 -0
  82. package/lib/module/ui/hooks/useFollow.types.js.map +1 -0
  83. package/lib/module/ui/navigation/OxyRouter.js +10 -0
  84. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  85. package/lib/module/ui/screens/AccountCenterScreen.js +12 -1
  86. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  87. package/lib/module/ui/screens/AccountOverviewScreen.js +3 -3
  88. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  89. package/lib/module/ui/screens/AccountSettingsScreen.js +64 -15
  90. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  91. package/lib/module/ui/screens/AccountSwitcherScreen.js +4 -4
  92. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  93. package/lib/module/ui/screens/FeedbackScreen.js +72 -75
  94. package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
  95. package/lib/module/ui/screens/FileManagementScreen.js +285 -125
  96. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  97. package/lib/module/ui/screens/LanguageSelectorScreen.js +319 -0
  98. package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -0
  99. package/lib/module/ui/screens/ProfileScreen.js +1 -1
  100. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  101. package/lib/module/ui/screens/SessionManagementScreen.js +177 -175
  102. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  103. package/lib/module/ui/screens/SignInScreen.js +44 -53
  104. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  105. package/lib/module/ui/screens/SignUpScreen.js +6 -4
  106. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  107. package/lib/module/ui/screens/WelcomeNewUserScreen.js +382 -0
  108. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -0
  109. package/lib/module/ui/screens/internal/SignInPasswordStep.js +23 -14
  110. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  111. package/lib/module/ui/screens/internal/SignInUsernameStep.js +15 -9
  112. package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  113. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
  114. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  115. package/lib/module/ui/styles/authStyles.js +1 -1
  116. package/lib/module/ui/styles/authStyles.js.map +1 -1
  117. package/lib/typescript/models/interfaces.d.ts +1 -5
  118. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  119. package/lib/typescript/models/session.d.ts +1 -4
  120. package/lib/typescript/models/session.d.ts.map +1 -1
  121. package/lib/typescript/ui/components/AnimationExample.d.ts +4 -0
  122. package/lib/typescript/ui/components/AnimationExample.d.ts.map +1 -0
  123. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  124. package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
  125. package/lib/typescript/ui/components/Header.d.ts +9 -0
  126. package/lib/typescript/ui/components/Header.d.ts.map +1 -1
  127. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  128. package/lib/typescript/ui/components/ProfileCard.d.ts +1 -3
  129. package/lib/typescript/ui/components/ProfileCard.d.ts.map +1 -1
  130. package/lib/typescript/ui/components/index.d.ts +0 -1
  131. package/lib/typescript/ui/components/index.d.ts.map +1 -1
  132. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
  133. package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts +27 -0
  134. package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -0
  135. package/lib/typescript/ui/context/OxyContext.d.ts +6 -2
  136. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  137. package/lib/typescript/ui/hooks/useFollow.types.d.ts +33 -0
  138. package/lib/typescript/ui/hooks/useFollow.types.d.ts.map +1 -0
  139. package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
  140. package/lib/typescript/ui/navigation/types.d.ts +5 -0
  141. package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
  142. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  143. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  144. package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -1
  145. package/lib/typescript/ui/screens/FileManagementScreen.d.ts +18 -1
  146. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  147. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +7 -0
  148. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -0
  149. package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
  150. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  151. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  152. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  153. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts +13 -0
  154. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -0
  155. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +5 -5
  156. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
  157. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +4 -4
  158. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +1 -1
  159. package/lib/typescript/ui/styles/authStyles.d.ts +1 -1
  160. package/package.json +10 -2
  161. package/src/models/interfaces.ts +2 -5
  162. package/src/models/session.ts +1 -4
  163. package/src/ui/components/AnimationExample.tsx +194 -0
  164. package/src/ui/components/FollowButton.tsx +65 -45
  165. package/src/ui/components/GroupedItem.tsx +1 -0
  166. package/src/ui/components/GroupedSection.tsx +1 -1
  167. package/src/ui/components/Header.tsx +36 -12
  168. package/src/ui/components/OxyProvider.tsx +66 -32
  169. package/src/ui/components/ProfileCard.tsx +6 -8
  170. package/src/ui/components/index.ts +0 -1
  171. package/src/ui/components/internal/TextField.tsx +12 -6
  172. package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +158 -0
  173. package/src/ui/context/OxyContext.tsx +84 -54
  174. package/src/ui/hooks/useFollow.types.ts +33 -0
  175. package/src/ui/navigation/OxyRouter.tsx +10 -0
  176. package/src/ui/navigation/types.ts +6 -0
  177. package/src/ui/screens/AccountCenterScreen.tsx +13 -7
  178. package/src/ui/screens/AccountOverviewScreen.tsx +3 -3
  179. package/src/ui/screens/AccountSettingsScreen.tsx +65 -13
  180. package/src/ui/screens/AccountSwitcherScreen.tsx +4 -4
  181. package/src/ui/screens/FeedbackScreen.tsx +57 -80
  182. package/src/ui/screens/FileManagementScreen.tsx +278 -175
  183. package/src/ui/screens/LanguageSelectorScreen.tsx +322 -0
  184. package/src/ui/screens/ProfileScreen.tsx +6 -1
  185. package/src/ui/screens/SessionManagementScreen.tsx +148 -151
  186. package/src/ui/screens/SignInScreen.tsx +43 -62
  187. package/src/ui/screens/SignUpScreen.tsx +3 -5
  188. package/src/ui/screens/WelcomeNewUserScreen.tsx +272 -0
  189. package/src/ui/screens/internal/SignInPasswordStep.tsx +28 -13
  190. package/src/ui/screens/internal/SignInUsernameStep.tsx +21 -11
  191. package/src/ui/screens/karma/KarmaCenterScreen.tsx +1 -1
  192. 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 renderRightAction = () => {
104
- if (!rightAction) return null;
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: rightAction.disabled ? 0.5 : 1
123
+ opacity: action.disabled ? 0.5 : 1
116
124
  }
117
125
  ]}
118
- onPress={rightAction.onPress}
119
- disabled={rightAction.disabled || rightAction.loading}
126
+ onPress={action.onPress}
127
+ disabled={action.disabled || action.loading}
120
128
  activeOpacity={0.7}
121
129
  >
122
- {rightAction.loading ? (
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
- {rightAction.text}
138
+ {action.text}
131
139
  </Text>
132
140
  ) : (
133
- <Ionicons name={rightAction.icon as any} size={18} color={colors.primary} />
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
- {renderRightAction()}
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
- {children}
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
- full?: string;
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?.url}
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: NativeSyntheticEvent<TextInputFocusEventData>) => {
309
+ const handleFocus = useCallback((event: FocusEvent) => {
308
310
  if (disabled) return;
309
311
  setFocused(true);
310
- onFocus?.(event);
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: NativeSyntheticEvent<TextInputFocusEventData>) => {
318
+ const handleBlur = useCallback((event: BlurEvent) => {
315
319
  setFocused(false);
316
- onBlur?.(event);
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: 'none',
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: 'none',
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);