@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.
Files changed (199) hide show
  1. package/lib/commonjs/core/OxyServices.js +104 -10
  2. package/lib/commonjs/core/OxyServices.js.map +1 -1
  3. package/lib/commonjs/ui/components/AnimationExample.js +213 -0
  4. package/lib/commonjs/ui/components/AnimationExample.js.map +1 -0
  5. package/lib/commonjs/ui/components/FollowButton.js +58 -47
  6. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  7. package/lib/commonjs/ui/components/GroupedItem.js +2 -1
  8. package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
  9. package/lib/commonjs/ui/components/GroupedSection.js +3 -0
  10. package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
  11. package/lib/commonjs/ui/components/Header.js +25 -11
  12. package/lib/commonjs/ui/components/Header.js.map +1 -1
  13. package/lib/commonjs/ui/components/OxyProvider.js +69 -33
  14. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  15. package/lib/commonjs/ui/components/ProfileCard.js +5 -1
  16. package/lib/commonjs/ui/components/ProfileCard.js.map +1 -1
  17. package/lib/commonjs/ui/components/index.js +0 -7
  18. package/lib/commonjs/ui/components/index.js.map +1 -1
  19. package/lib/commonjs/ui/components/internal/TextField.js +8 -4
  20. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
  21. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +161 -0
  22. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
  23. package/lib/commonjs/ui/context/OxyContext.js +97 -38
  24. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  25. package/lib/commonjs/ui/hooks/useFollow.types.js +2 -0
  26. package/lib/commonjs/ui/hooks/useFollow.types.js.map +1 -0
  27. package/lib/commonjs/ui/navigation/OxyRouter.js +10 -0
  28. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  29. package/lib/commonjs/ui/screens/AccountCenterScreen.js +26 -14
  30. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  31. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +3 -3
  32. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  33. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +64 -15
  34. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  35. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +4 -4
  36. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  37. package/lib/commonjs/ui/screens/FeedbackScreen.js +72 -75
  38. package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
  39. package/lib/commonjs/ui/screens/FileManagementScreen.js +286 -126
  40. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  41. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +322 -0
  42. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -0
  43. package/lib/commonjs/ui/screens/ProfileScreen.js +1 -1
  44. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  45. package/lib/commonjs/ui/screens/SessionManagementScreen.js +176 -174
  46. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  47. package/lib/commonjs/ui/screens/SignInScreen.js +43 -52
  48. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  49. package/lib/commonjs/ui/screens/SignUpScreen.js +6 -4
  50. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  51. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +386 -0
  52. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -0
  53. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +25 -15
  54. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  55. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +16 -9
  56. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  57. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -1
  58. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  59. package/lib/commonjs/ui/styles/authStyles.js +1 -1
  60. package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
  61. package/lib/module/core/OxyServices.js +103 -9
  62. package/lib/module/core/OxyServices.js.map +1 -1
  63. package/lib/module/ui/components/AnimationExample.js +209 -0
  64. package/lib/module/ui/components/AnimationExample.js.map +1 -0
  65. package/lib/module/ui/components/FollowButton.js +58 -47
  66. package/lib/module/ui/components/FollowButton.js.map +1 -1
  67. package/lib/module/ui/components/GroupedItem.js +2 -1
  68. package/lib/module/ui/components/GroupedItem.js.map +1 -1
  69. package/lib/module/ui/components/GroupedSection.js +3 -0
  70. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  71. package/lib/module/ui/components/Header.js +25 -11
  72. package/lib/module/ui/components/Header.js.map +1 -1
  73. package/lib/module/ui/components/OxyProvider.js +70 -34
  74. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  75. package/lib/module/ui/components/ProfileCard.js +5 -1
  76. package/lib/module/ui/components/ProfileCard.js.map +1 -1
  77. package/lib/module/ui/components/index.js +0 -1
  78. package/lib/module/ui/components/index.js.map +1 -1
  79. package/lib/module/ui/components/internal/TextField.js +8 -4
  80. package/lib/module/ui/components/internal/TextField.js.map +1 -1
  81. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +156 -0
  82. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
  83. package/lib/module/ui/context/OxyContext.js +97 -39
  84. package/lib/module/ui/context/OxyContext.js.map +1 -1
  85. package/lib/module/ui/hooks/useFollow.types.js +2 -0
  86. package/lib/module/ui/hooks/useFollow.types.js.map +1 -0
  87. package/lib/module/ui/navigation/OxyRouter.js +10 -0
  88. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  89. package/lib/module/ui/screens/AccountCenterScreen.js +12 -1
  90. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  91. package/lib/module/ui/screens/AccountOverviewScreen.js +3 -3
  92. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  93. package/lib/module/ui/screens/AccountSettingsScreen.js +64 -15
  94. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  95. package/lib/module/ui/screens/AccountSwitcherScreen.js +4 -4
  96. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  97. package/lib/module/ui/screens/FeedbackScreen.js +72 -75
  98. package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
  99. package/lib/module/ui/screens/FileManagementScreen.js +285 -125
  100. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  101. package/lib/module/ui/screens/LanguageSelectorScreen.js +319 -0
  102. package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -0
  103. package/lib/module/ui/screens/ProfileScreen.js +1 -1
  104. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  105. package/lib/module/ui/screens/SessionManagementScreen.js +177 -175
  106. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  107. package/lib/module/ui/screens/SignInScreen.js +44 -53
  108. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  109. package/lib/module/ui/screens/SignUpScreen.js +6 -4
  110. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  111. package/lib/module/ui/screens/WelcomeNewUserScreen.js +382 -0
  112. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -0
  113. package/lib/module/ui/screens/internal/SignInPasswordStep.js +23 -14
  114. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  115. package/lib/module/ui/screens/internal/SignInUsernameStep.js +15 -9
  116. package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  117. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
  118. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  119. package/lib/module/ui/styles/authStyles.js +1 -1
  120. package/lib/module/ui/styles/authStyles.js.map +1 -1
  121. package/lib/typescript/core/OxyServices.d.ts +95 -4
  122. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  123. package/lib/typescript/models/interfaces.d.ts +1 -5
  124. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  125. package/lib/typescript/models/session.d.ts +1 -4
  126. package/lib/typescript/models/session.d.ts.map +1 -1
  127. package/lib/typescript/ui/components/AnimationExample.d.ts +4 -0
  128. package/lib/typescript/ui/components/AnimationExample.d.ts.map +1 -0
  129. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  130. package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
  131. package/lib/typescript/ui/components/Header.d.ts +9 -0
  132. package/lib/typescript/ui/components/Header.d.ts.map +1 -1
  133. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  134. package/lib/typescript/ui/components/ProfileCard.d.ts +1 -3
  135. package/lib/typescript/ui/components/ProfileCard.d.ts.map +1 -1
  136. package/lib/typescript/ui/components/index.d.ts +0 -1
  137. package/lib/typescript/ui/components/index.d.ts.map +1 -1
  138. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
  139. package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts +27 -0
  140. package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -0
  141. package/lib/typescript/ui/context/OxyContext.d.ts +6 -2
  142. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  143. package/lib/typescript/ui/hooks/useFollow.types.d.ts +33 -0
  144. package/lib/typescript/ui/hooks/useFollow.types.d.ts.map +1 -0
  145. package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
  146. package/lib/typescript/ui/navigation/types.d.ts +5 -0
  147. package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
  148. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  149. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  150. package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -1
  151. package/lib/typescript/ui/screens/FileManagementScreen.d.ts +18 -1
  152. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  153. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +7 -0
  154. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -0
  155. package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
  156. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  157. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  158. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  159. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts +13 -0
  160. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -0
  161. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +5 -5
  162. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
  163. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +4 -4
  164. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +1 -1
  165. package/lib/typescript/ui/styles/authStyles.d.ts +1 -1
  166. package/package.json +10 -2
  167. package/src/core/OxyServices.ts +107 -13
  168. package/src/models/interfaces.ts +2 -5
  169. package/src/models/session.ts +1 -4
  170. package/src/ui/components/AnimationExample.tsx +194 -0
  171. package/src/ui/components/FollowButton.tsx +65 -45
  172. package/src/ui/components/GroupedItem.tsx +1 -0
  173. package/src/ui/components/GroupedSection.tsx +1 -1
  174. package/src/ui/components/Header.tsx +36 -12
  175. package/src/ui/components/OxyProvider.tsx +66 -32
  176. package/src/ui/components/ProfileCard.tsx +6 -8
  177. package/src/ui/components/index.ts +0 -1
  178. package/src/ui/components/internal/TextField.tsx +12 -6
  179. package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +158 -0
  180. package/src/ui/context/OxyContext.tsx +84 -54
  181. package/src/ui/hooks/useFollow.types.ts +33 -0
  182. package/src/ui/navigation/OxyRouter.tsx +10 -0
  183. package/src/ui/navigation/types.ts +6 -0
  184. package/src/ui/screens/AccountCenterScreen.tsx +13 -7
  185. package/src/ui/screens/AccountOverviewScreen.tsx +3 -3
  186. package/src/ui/screens/AccountSettingsScreen.tsx +65 -13
  187. package/src/ui/screens/AccountSwitcherScreen.tsx +4 -4
  188. package/src/ui/screens/FeedbackScreen.tsx +57 -80
  189. package/src/ui/screens/FileManagementScreen.tsx +278 -175
  190. package/src/ui/screens/LanguageSelectorScreen.tsx +322 -0
  191. package/src/ui/screens/ProfileScreen.tsx +6 -1
  192. package/src/ui/screens/SessionManagementScreen.tsx +148 -151
  193. package/src/ui/screens/SignInScreen.tsx +43 -62
  194. package/src/ui/screens/SignUpScreen.tsx +3 -5
  195. package/src/ui/screens/WelcomeNewUserScreen.tsx +272 -0
  196. package/src/ui/screens/internal/SignInPasswordStep.tsx +28 -13
  197. package/src/ui/screens/internal/SignInUsernameStep.tsx +21 -11
  198. package/src/ui/screens/karma/KarmaCenterScreen.tsx +1 -1
  199. package/src/ui/styles/authStyles.ts +1 -1
@@ -0,0 +1,194 @@
1
+ import React, { useState } from 'react';
2
+ import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
3
+ import Animated, {
4
+ useSharedValue,
5
+ useAnimatedStyle,
6
+ withTiming,
7
+ withSpring,
8
+ withSequence,
9
+ withDelay,
10
+ interpolateColor,
11
+ runOnJS,
12
+ Easing,
13
+ } from 'react-native-reanimated';
14
+
15
+ // Example component showcasing improved Reanimated usage
16
+ const AnimationExample: React.FC = () => {
17
+ const [currentStep, setCurrentStep] = useState(0);
18
+
19
+ // Shared values for better performance
20
+ const opacity = useSharedValue(1);
21
+ const scale = useSharedValue(1);
22
+ const translateX = useSharedValue(0);
23
+ const rotation = useSharedValue(0);
24
+ const progress = useSharedValue(0);
25
+ const colorProgress = useSharedValue(0);
26
+
27
+ // Animated styles with proper interpolation
28
+ const animatedStyle = useAnimatedStyle(() => {
29
+ return {
30
+ opacity: opacity.value,
31
+ transform: [
32
+ { scale: scale.value },
33
+ { translateX: translateX.value },
34
+ { rotate: `${rotation.value}deg` },
35
+ ],
36
+ };
37
+ });
38
+
39
+ const progressStyle = useAnimatedStyle(() => {
40
+ return {
41
+ width: `${progress.value * 100}%`,
42
+ backgroundColor: interpolateColor(
43
+ colorProgress.value,
44
+ [0, 1],
45
+ ['#3498db', '#e74c3c']
46
+ ),
47
+ };
48
+ });
49
+
50
+ const backgroundStyle = useAnimatedStyle(() => {
51
+ return {
52
+ backgroundColor: interpolateColor(
53
+ colorProgress.value,
54
+ [0, 1],
55
+ ['#ecf0f1', '#f39c12']
56
+ ),
57
+ };
58
+ });
59
+
60
+ // Complex animation sequence
61
+ const animateSequence = () => {
62
+ 'worklet';
63
+
64
+ // Staggered animations for smooth transitions
65
+ opacity.value = withTiming(0.5, { duration: 200 });
66
+ scale.value = withSpring(0.8, { damping: 15, stiffness: 150 });
67
+
68
+ // Delayed follow-up animations
69
+ translateX.value = withDelay(
70
+ 100,
71
+ withSpring(50, { damping: 20, stiffness: 100 }, (finished) => {
72
+ if (finished) {
73
+ translateX.value = withSpring(0, { damping: 15, stiffness: 150 });
74
+ }
75
+ })
76
+ );
77
+
78
+ // Sequential animations
79
+ rotation.value = withSequence(
80
+ withTiming(10, { duration: 150 }),
81
+ withTiming(-10, { duration: 150 }),
82
+ withTiming(0, { duration: 150 })
83
+ );
84
+
85
+ // Progress animation with easing
86
+ progress.value = withTiming(1, {
87
+ duration: 1000,
88
+ easing: Easing.out(Easing.exp)
89
+ }, (finished) => {
90
+ if (finished) {
91
+ runOnJS(setCurrentStep)(currentStep + 1);
92
+ }
93
+ });
94
+
95
+ // Color transition
96
+ colorProgress.value = withTiming(1, { duration: 800 });
97
+
98
+ // Reset animations
99
+ setTimeout(() => {
100
+ opacity.value = withSpring(1);
101
+ scale.value = withSpring(1);
102
+ progress.value = withTiming(0, { duration: 500 });
103
+ colorProgress.value = withTiming(0, { duration: 500 });
104
+ }, 1500);
105
+ };
106
+
107
+ return (
108
+ <Animated.View style={[styles.container, backgroundStyle]}>
109
+ <Text style={styles.title}>Advanced Reanimated Example</Text>
110
+ <Text style={styles.subtitle}>Step: {currentStep}</Text>
111
+
112
+ <Animated.View style={[styles.box, animatedStyle]}>
113
+ <Text style={styles.boxText}>Animated Box</Text>
114
+ </Animated.View>
115
+
116
+ <View style={styles.progressContainer}>
117
+ <Animated.View style={[styles.progressBar, progressStyle]} />
118
+ </View>
119
+
120
+ <TouchableOpacity style={styles.button} onPress={animateSequence}>
121
+ <Text style={styles.buttonText}>Animate Sequence</Text>
122
+ </TouchableOpacity>
123
+ </Animated.View>
124
+ );
125
+ };
126
+
127
+ const styles = StyleSheet.create({
128
+ container: {
129
+ flex: 1,
130
+ justifyContent: 'center',
131
+ alignItems: 'center',
132
+ padding: 20,
133
+ },
134
+ title: {
135
+ fontSize: 24,
136
+ fontWeight: 'bold',
137
+ marginBottom: 10,
138
+ color: '#2c3e50',
139
+ },
140
+ subtitle: {
141
+ fontSize: 16,
142
+ marginBottom: 30,
143
+ color: '#7f8c8d',
144
+ },
145
+ box: {
146
+ width: 150,
147
+ height: 150,
148
+ backgroundColor: '#3498db',
149
+ borderRadius: 20,
150
+ justifyContent: 'center',
151
+ alignItems: 'center',
152
+ marginBottom: 30,
153
+ shadowColor: '#000',
154
+ shadowOffset: { width: 0, height: 4 },
155
+ shadowOpacity: 0.3,
156
+ shadowRadius: 8,
157
+ elevation: 8,
158
+ },
159
+ boxText: {
160
+ color: 'white',
161
+ fontSize: 16,
162
+ fontWeight: 'bold',
163
+ },
164
+ progressContainer: {
165
+ width: '100%',
166
+ height: 10,
167
+ backgroundColor: '#ecf0f1',
168
+ borderRadius: 5,
169
+ marginBottom: 30,
170
+ overflow: 'hidden',
171
+ },
172
+ progressBar: {
173
+ height: '100%',
174
+ borderRadius: 5,
175
+ },
176
+ button: {
177
+ backgroundColor: '#e74c3c',
178
+ paddingHorizontal: 30,
179
+ paddingVertical: 15,
180
+ borderRadius: 25,
181
+ shadowColor: '#000',
182
+ shadowOffset: { width: 0, height: 2 },
183
+ shadowOpacity: 0.2,
184
+ shadowRadius: 4,
185
+ elevation: 4,
186
+ },
187
+ buttonText: {
188
+ color: 'white',
189
+ fontSize: 16,
190
+ fontWeight: 'bold',
191
+ },
192
+ });
193
+
194
+ export default AnimationExample;
@@ -24,6 +24,10 @@ import { toast } from '../../lib/sonner';
24
24
  import { useFollow } from '../hooks/useFollow';
25
25
  import { useThemeColors } from '../styles/theme';
26
26
 
27
+ // Create animated TouchableOpacity
28
+ const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity);
29
+ const AnimatedText = Animated.createAnimatedComponent(Text);
30
+
27
31
  export interface FollowButtonProps {
28
32
  userId: string;
29
33
  initiallyFollowing?: boolean;
@@ -65,6 +69,30 @@ const FollowButton: React.FC<FollowButtonProps> = ({
65
69
  const animationProgress = useSharedValue(isFollowing ? 1 : 0);
66
70
  const scale = useSharedValue(1);
67
71
 
72
+ // Button press handler with animation
73
+ const handlePress = useCallback(async (event?: { preventDefault?: () => void; stopPropagation?: () => void }) => {
74
+ if (preventParentActions && event && event.preventDefault) {
75
+ event.preventDefault();
76
+ event.stopPropagation?.();
77
+ }
78
+ if (disabled || isLoading) return;
79
+
80
+ // Press animation
81
+ scale.value = withTiming(0.95, { duration: 100 }, (finished) => {
82
+ if (finished) {
83
+ scale.value = withSpring(1, { damping: 15, stiffness: 200 });
84
+ }
85
+ });
86
+
87
+ try {
88
+ await toggleFollow?.();
89
+ if (onFollowChange) onFollowChange(!isFollowing);
90
+ } catch (err: unknown) {
91
+ const error = err instanceof Error ? err : new Error(String(err));
92
+ toast.error(error.message || 'Failed to update follow status');
93
+ }
94
+ }, [disabled, isLoading, toggleFollow, onFollowChange, isFollowing, preventParentActions, scale]);
95
+
68
96
  // Initialize Zustand state with initial value if not already set
69
97
  useEffect(() => {
70
98
  if (userId && !isFollowing && initiallyFollowing) {
@@ -86,24 +114,35 @@ const FollowButton: React.FC<FollowButtonProps> = ({
86
114
  animationProgress.value = withTiming(isFollowing ? 1 : 0, { duration: 300, easing: Easing.inOut(Easing.ease) });
87
115
  }, [isFollowing, animationProgress]);
88
116
 
89
- // Button press handler
90
- const handlePress = useCallback(async (event?: { preventDefault?: () => void; stopPropagation?: () => void }) => {
91
- if (preventParentActions && event && event.preventDefault) {
92
- event.preventDefault();
93
- event.stopPropagation?.();
94
- }
95
- if (disabled || isLoading) return;
96
- try {
97
- await toggleFollow?.();
98
- if (onFollowChange) onFollowChange(!isFollowing);
99
- } catch (err: unknown) {
100
- const error = err instanceof Error ? err : new Error(String(err));
101
- toast.error(error.message || 'Failed to update follow status');
102
- }
103
- }, [disabled, isLoading, toggleFollow, onFollowChange, isFollowing, preventParentActions]);
117
+ // Animated styles for better performance
118
+ const animatedButtonStyle = useAnimatedStyle(() => {
119
+ return {
120
+ transform: [{ scale: scale.value }],
121
+ backgroundColor: interpolateColor(
122
+ animationProgress.value,
123
+ [0, 1],
124
+ [colors.background, colors.primary]
125
+ ),
126
+ borderColor: interpolateColor(
127
+ animationProgress.value,
128
+ [0, 1],
129
+ [colors.border, colors.primary]
130
+ ),
131
+ };
132
+ }, [colors]);
133
+
134
+ const animatedTextStyle = useAnimatedStyle(() => {
135
+ return {
136
+ color: interpolateColor(
137
+ animationProgress.value,
138
+ [0, 1],
139
+ [colors.text, '#FFFFFF']
140
+ ),
141
+ };
142
+ }, [colors]);
104
143
 
105
- // Get button style based on size and follow state
106
- const getButtonStyle = (): StyleProp<ViewStyle> => {
144
+ // Get base button style (without state-specific colors since they're animated)
145
+ const getBaseButtonStyle = (): StyleProp<ViewStyle> => {
107
146
  const baseStyle = {
108
147
  flexDirection: 'row' as const,
109
148
  alignItems: 'center' as const,
@@ -148,27 +187,11 @@ const FollowButton: React.FC<FollowButtonProps> = ({
148
187
  };
149
188
  }
150
189
 
151
- // State-specific colors
152
- let stateStyle = {};
153
- if (isFollowing) {
154
- stateStyle = {
155
- backgroundColor: colors.primary,
156
- borderColor: colors.primary,
157
- shadowColor: colors.primary,
158
- };
159
- } else {
160
- stateStyle = {
161
- backgroundColor: colors.background,
162
- borderColor: colors.border,
163
- shadowColor: colors.border,
164
- };
165
- }
166
-
167
- return [baseStyle, sizeStyle, stateStyle, style];
190
+ return [baseStyle, sizeStyle, style];
168
191
  };
169
192
 
170
- // Get text style based on size and follow state
171
- const getTextStyle = (): StyleProp<TextStyle> => {
193
+ // Get base text style (without state-specific colors since they're animated)
194
+ const getBaseTextStyle = (): StyleProp<TextStyle> => {
172
195
  const baseTextStyle = {
173
196
  fontFamily: fontFamilies.phuduSemiBold,
174
197
  fontWeight: '600' as const,
@@ -185,15 +208,12 @@ const FollowButton: React.FC<FollowButtonProps> = ({
185
208
  sizeTextStyle = { fontSize: 15 };
186
209
  }
187
210
 
188
- // State-specific text color
189
- const textColor = isFollowing ? '#FFFFFF' : colors.text;
190
-
191
- return [baseTextStyle, sizeTextStyle, { color: textColor }, textStyle];
211
+ return [baseTextStyle, sizeTextStyle, textStyle];
192
212
  };
193
213
 
194
214
  return (
195
- <TouchableOpacity
196
- style={getButtonStyle()}
215
+ <AnimatedTouchableOpacity
216
+ style={[getBaseButtonStyle(), animatedButtonStyle]}
197
217
  onPress={handlePress}
198
218
  disabled={disabled || isLoading}
199
219
  activeOpacity={0.8}
@@ -204,11 +224,11 @@ const FollowButton: React.FC<FollowButtonProps> = ({
204
224
  color={isFollowing ? '#FFFFFF' : colors.primary}
205
225
  />
206
226
  ) : (
207
- <Text style={getTextStyle()}>
227
+ <AnimatedText style={[getBaseTextStyle(), animatedTextStyle]}>
208
228
  {isFollowing ? 'Following' : 'Follow'}
209
- </Text>
229
+ </AnimatedText>
210
230
  )}
211
- </TouchableOpacity>
231
+ </AnimatedTouchableOpacity>
212
232
  );
213
233
  };
214
234
 
@@ -117,6 +117,7 @@ const styles = StyleSheet.create({
117
117
  justifyContent: 'space-between',
118
118
  marginBottom: 2,
119
119
  overflow: 'hidden',
120
+ width: '100%',
120
121
  },
121
122
  firstGroupedItem: {
122
123
  borderTopLeftRadius: 24,
@@ -28,7 +28,7 @@ interface GroupedSectionProps {
28
28
 
29
29
  const GroupedSection: React.FC<GroupedSectionProps> = ({ items, theme }) => {
30
30
  return (
31
- <View>
31
+ <View style={{ width: '100%' }}>
32
32
  {items.map((item, index) => (
33
33
  <GroupedItem
34
34
  key={item.id}
@@ -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';