@oxyhq/services 5.4.3 → 5.4.5

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 (233) hide show
  1. package/README.md +14 -0
  2. package/lib/commonjs/assets/assets/illustrations/HighFive.tsx +41 -0
  3. package/lib/commonjs/assets/icons/OxyServices.js +1 -1
  4. package/lib/commonjs/assets/illustrations/HighFive.js +61 -0
  5. package/lib/commonjs/assets/illustrations/HighFive.js.map +1 -0
  6. package/lib/commonjs/core/index.js +24 -5
  7. package/lib/commonjs/core/index.js.map +1 -1
  8. package/lib/commonjs/index.js +72 -23
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/node/createAuth.js +95 -0
  11. package/lib/commonjs/node/createAuth.js.map +1 -0
  12. package/lib/commonjs/node/index.js +15 -6
  13. package/lib/commonjs/node/index.js.map +1 -1
  14. package/lib/commonjs/package.json +1 -0
  15. package/lib/commonjs/ui/components/Avatar.js +3 -3
  16. package/lib/commonjs/ui/components/Avatar.js.map +1 -1
  17. package/lib/commonjs/ui/components/FollowButton.js +82 -34
  18. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  19. package/lib/commonjs/ui/components/GroupedSection.js +1 -1
  20. package/lib/commonjs/ui/components/OxyLogo.js +1 -1
  21. package/lib/commonjs/ui/components/OxyProvider.js +146 -141
  22. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  23. package/lib/commonjs/ui/components/OxySignInButton.js +4 -4
  24. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  25. package/lib/commonjs/ui/components/ProfileCard.js +2 -2
  26. package/lib/commonjs/ui/components/Section.js +1 -1
  27. package/lib/commonjs/ui/components/SectionTitle.js +1 -1
  28. package/lib/commonjs/ui/components/icon/index.js +1 -1
  29. package/lib/commonjs/ui/components/index.js +12 -12
  30. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js +213 -0
  31. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js.map +1 -0
  32. package/lib/commonjs/ui/components/internal/TextField.js +576 -0
  33. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -0
  34. package/lib/commonjs/ui/context/OxyContext.js +12 -2
  35. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  36. package/lib/commonjs/ui/hooks/index.js +13 -0
  37. package/lib/commonjs/ui/hooks/index.js.map +1 -0
  38. package/lib/commonjs/ui/hooks/useFollow.js +184 -0
  39. package/lib/commonjs/ui/hooks/useFollow.js.map +1 -0
  40. package/lib/commonjs/ui/index.js +44 -12
  41. package/lib/commonjs/ui/index.js.map +1 -1
  42. package/lib/commonjs/ui/navigation/OxyRouter.js +23 -18
  43. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  44. package/lib/commonjs/ui/screens/AccountCenterScreen.js +21 -20
  45. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  46. package/lib/commonjs/ui/screens/AccountManagementDemo.js +3 -3
  47. package/lib/commonjs/ui/screens/AccountManagementDemo.js.map +1 -1
  48. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +11 -10
  49. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  50. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +8 -7
  51. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  52. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +6 -5
  53. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  54. package/lib/commonjs/ui/screens/AppInfoScreen.js +12 -14
  55. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  56. package/lib/commonjs/ui/screens/BillingManagementScreen.js +3 -3
  57. package/lib/commonjs/ui/screens/FeedbackScreen.js +1169 -0
  58. package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -0
  59. package/lib/commonjs/ui/screens/FileManagementScreen.js +3 -3
  60. package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js +3 -3
  61. package/lib/commonjs/ui/screens/ProfileScreen.js +2 -2
  62. package/lib/commonjs/ui/screens/SessionManagementScreen.js +2 -2
  63. package/lib/commonjs/ui/screens/SignInScreen.js +183 -305
  64. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  65. package/lib/commonjs/ui/screens/SignUpScreen.js +811 -712
  66. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  67. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +8 -7
  68. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  69. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +2 -2
  70. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +1 -1
  71. package/lib/commonjs/ui/store/index.js +267 -0
  72. package/lib/commonjs/ui/store/index.js.map +1 -0
  73. package/lib/commonjs/ui/styles/index.js +2 -2
  74. package/lib/commonjs/ui/styles/theme.js +1 -1
  75. package/lib/commonjs/utils/index.js +1 -1
  76. package/lib/module/assets/assets/illustrations/HighFive.tsx +41 -0
  77. package/lib/module/assets/icons/OxyServices.js +1 -1
  78. package/lib/module/assets/icons/OxyServices.js.map +1 -1
  79. package/lib/module/assets/illustrations/HighFive.js +55 -0
  80. package/lib/module/assets/illustrations/HighFive.js.map +1 -0
  81. package/lib/module/core/index.js +24 -5
  82. package/lib/module/core/index.js.map +1 -1
  83. package/lib/module/index.js +15 -11
  84. package/lib/module/index.js.map +1 -1
  85. package/lib/module/node/createAuth.js +90 -0
  86. package/lib/module/node/createAuth.js.map +1 -0
  87. package/lib/module/node/index.js +8 -4
  88. package/lib/module/node/index.js.map +1 -1
  89. package/lib/module/package.json +1 -0
  90. package/lib/module/ui/components/Avatar.js +2 -2
  91. package/lib/module/ui/components/Avatar.js.map +1 -1
  92. package/lib/module/ui/components/FollowButton.js +83 -35
  93. package/lib/module/ui/components/FollowButton.js.map +1 -1
  94. package/lib/module/ui/components/GroupedSection.js +1 -1
  95. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  96. package/lib/module/ui/components/OxyLogo.js +1 -1
  97. package/lib/module/ui/components/OxyLogo.js.map +1 -1
  98. package/lib/module/ui/components/OxyProvider.js +143 -138
  99. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  100. package/lib/module/ui/components/OxySignInButton.js +4 -4
  101. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  102. package/lib/module/ui/components/ProfileCard.js +2 -2
  103. package/lib/module/ui/components/ProfileCard.js.map +1 -1
  104. package/lib/module/ui/components/Section.js +1 -1
  105. package/lib/module/ui/components/Section.js.map +1 -1
  106. package/lib/module/ui/components/SectionTitle.js +1 -1
  107. package/lib/module/ui/components/SectionTitle.js.map +1 -1
  108. package/lib/module/ui/components/icon/index.js +1 -1
  109. package/lib/module/ui/components/icon/index.js.map +1 -1
  110. package/lib/module/ui/components/index.js +12 -12
  111. package/lib/module/ui/components/index.js.map +1 -1
  112. package/lib/module/ui/components/internal/GroupedPillButtons.js +208 -0
  113. package/lib/module/ui/components/internal/GroupedPillButtons.js.map +1 -0
  114. package/lib/module/ui/components/internal/TextField.js +571 -0
  115. package/lib/module/ui/components/internal/TextField.js.map +1 -0
  116. package/lib/module/ui/context/OxyContext.js +12 -2
  117. package/lib/module/ui/context/OxyContext.js.map +1 -1
  118. package/lib/module/ui/hooks/index.js +4 -0
  119. package/lib/module/ui/hooks/index.js.map +1 -0
  120. package/lib/module/ui/hooks/useFollow.js +180 -0
  121. package/lib/module/ui/hooks/useFollow.js.map +1 -0
  122. package/lib/module/ui/index.js +21 -10
  123. package/lib/module/ui/index.js.map +1 -1
  124. package/lib/module/ui/navigation/OxyRouter.js +23 -18
  125. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  126. package/lib/module/ui/screens/AccountCenterScreen.js +9 -8
  127. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  128. package/lib/module/ui/screens/AccountManagementDemo.js +2 -2
  129. package/lib/module/ui/screens/AccountManagementDemo.js.map +1 -1
  130. package/lib/module/ui/screens/AccountOverviewScreen.js +11 -10
  131. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  132. package/lib/module/ui/screens/AccountSettingsScreen.js +8 -7
  133. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  134. package/lib/module/ui/screens/AccountSwitcherScreen.js +6 -5
  135. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  136. package/lib/module/ui/screens/AppInfoScreen.js +12 -14
  137. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  138. package/lib/module/ui/screens/BillingManagementScreen.js +3 -3
  139. package/lib/module/ui/screens/BillingManagementScreen.js.map +1 -1
  140. package/lib/module/ui/screens/FeedbackScreen.js +1164 -0
  141. package/lib/module/ui/screens/FeedbackScreen.js.map +1 -0
  142. package/lib/module/ui/screens/FileManagementScreen.js +3 -3
  143. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  144. package/lib/module/ui/screens/PremiumSubscriptionScreen.js +3 -3
  145. package/lib/module/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
  146. package/lib/module/ui/screens/ProfileScreen.js +2 -2
  147. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  148. package/lib/module/ui/screens/SessionManagementScreen.js +2 -2
  149. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  150. package/lib/module/ui/screens/SignInScreen.js +183 -305
  151. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  152. package/lib/module/ui/screens/SignUpScreen.js +810 -712
  153. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  154. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +8 -7
  155. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  156. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +2 -2
  157. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
  158. package/lib/module/ui/screens/karma/KarmaRulesScreen.js +1 -1
  159. package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
  160. package/lib/module/ui/store/index.js +255 -0
  161. package/lib/module/ui/store/index.js.map +1 -0
  162. package/lib/module/ui/styles/index.js +2 -2
  163. package/lib/module/ui/styles/index.js.map +1 -1
  164. package/lib/module/ui/styles/theme.js +1 -1
  165. package/lib/module/ui/styles/theme.js.map +1 -1
  166. package/lib/module/utils/index.js +1 -1
  167. package/lib/module/utils/index.js.map +1 -1
  168. package/lib/typescript/assets/illustrations/HighFive.d.ts +9 -0
  169. package/lib/typescript/assets/illustrations/HighFive.d.ts.map +1 -0
  170. package/lib/typescript/core/index.d.ts +16 -3
  171. package/lib/typescript/core/index.d.ts.map +1 -1
  172. package/lib/typescript/index.d.ts +4 -2
  173. package/lib/typescript/index.d.ts.map +1 -1
  174. package/lib/typescript/node/createAuth.d.ts +7 -0
  175. package/lib/typescript/node/createAuth.d.ts.map +1 -0
  176. package/lib/typescript/node/index.d.ts +2 -0
  177. package/lib/typescript/node/index.d.ts.map +1 -1
  178. package/lib/typescript/types/expo-vector-icons.d.ts +3 -0
  179. package/lib/typescript/types/express.d.ts +5 -0
  180. package/lib/typescript/types/react-redux.d.ts +5 -0
  181. package/lib/typescript/ui/components/FollowButton.d.ts +1 -0
  182. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  183. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  184. package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts +18 -0
  185. package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts.map +1 -0
  186. package/lib/typescript/ui/components/internal/TextField.d.ts +25 -0
  187. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -0
  188. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  189. package/lib/typescript/ui/hooks/index.d.ts +2 -0
  190. package/lib/typescript/ui/hooks/index.d.ts.map +1 -0
  191. package/lib/typescript/ui/hooks/useFollow.d.ts +43 -0
  192. package/lib/typescript/ui/hooks/useFollow.d.ts.map +1 -0
  193. package/lib/typescript/ui/index.d.ts +5 -0
  194. package/lib/typescript/ui/index.d.ts.map +1 -1
  195. package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
  196. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  197. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  198. package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
  199. package/lib/typescript/ui/screens/FeedbackScreen.d.ts +5 -0
  200. package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -0
  201. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  202. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  203. package/lib/typescript/ui/store/index.d.ts +66 -0
  204. package/lib/typescript/ui/store/index.d.ts.map +1 -0
  205. package/package.json +10 -25
  206. package/src/assets/illustrations/HighFive.tsx +41 -0
  207. package/src/core/index.ts +88 -3
  208. package/src/index.ts +19 -3
  209. package/src/node/createAuth.ts +116 -0
  210. package/src/node/index.ts +4 -0
  211. package/src/types/expo-vector-icons.d.ts +3 -0
  212. package/src/types/express.d.ts +5 -0
  213. package/src/types/react-redux.d.ts +5 -0
  214. package/src/ui/components/FollowButton.tsx +114 -56
  215. package/src/ui/components/OxyProvider.tsx +136 -135
  216. package/src/ui/components/OxySignInButton.tsx +2 -2
  217. package/src/ui/components/internal/GroupedPillButtons.tsx +253 -0
  218. package/src/ui/components/internal/TextField.tsx +694 -0
  219. package/src/ui/context/OxyContext.tsx +12 -2
  220. package/src/ui/hooks/index.ts +1 -0
  221. package/src/ui/hooks/useFollow.ts +173 -0
  222. package/src/ui/index.ts +15 -2
  223. package/src/ui/navigation/OxyRouter.tsx +8 -3
  224. package/src/ui/screens/AccountCenterScreen.tsx +17 -15
  225. package/src/ui/screens/AccountOverviewScreen.tsx +25 -25
  226. package/src/ui/screens/AccountSettingsScreen.tsx +30 -30
  227. package/src/ui/screens/AccountSwitcherScreen.tsx +34 -33
  228. package/src/ui/screens/AppInfoScreen.tsx +153 -155
  229. package/src/ui/screens/FeedbackScreen.tsx +1042 -0
  230. package/src/ui/screens/SignInScreen.tsx +181 -224
  231. package/src/ui/screens/SignUpScreen.tsx +772 -608
  232. package/src/ui/screens/karma/KarmaCenterScreen.tsx +4 -4
  233. package/src/ui/store/index.ts +245 -0
@@ -4,6 +4,8 @@ import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-cont
4
4
  import { GestureHandlerRootView } from 'react-native-gesture-handler';
5
5
  import { OxyProviderProps } from '../navigation/types';
6
6
  import { OxyContextProvider, useOxy } from '../context/OxyContext';
7
+ import { Provider } from 'react-redux';
8
+ import { store } from '../store';
7
9
  import OxyRouter from '../navigation/OxyRouter';
8
10
  import { FontLoader, setupFonts } from './FontLoader';
9
11
  import { Toaster } from '../../lib/sonner';
@@ -39,42 +41,46 @@ const OxyProvider: React.FC<OxyProviderProps> = (props) => {
39
41
  // If contextOnly is true, we just provide the context without the bottom sheet UI
40
42
  if (contextOnly) {
41
43
  return (
44
+ <Provider store={store}>
45
+ <OxyContextProvider
46
+ oxyServices={oxyServices}
47
+ storageKeyPrefix={storageKeyPrefix}
48
+ onAuthStateChange={onAuthStateChange}
49
+ >
50
+ {children}
51
+ </OxyContextProvider>
52
+ </Provider>
53
+ );
54
+ }
55
+
56
+ // Otherwise, provide both the context and the bottom sheet UI
57
+ return (
58
+ <Provider store={store}>
42
59
  <OxyContextProvider
43
60
  oxyServices={oxyServices}
44
61
  storageKeyPrefix={storageKeyPrefix}
45
62
  onAuthStateChange={onAuthStateChange}
63
+ bottomSheetRef={internalBottomSheetRef}
46
64
  >
47
- {children}
65
+ <FontLoader>
66
+ <GestureHandlerRootView style={styles.gestureHandlerRoot}>
67
+ <BottomSheetModalProvider>
68
+ <StatusBar translucent backgroundColor="transparent" />
69
+ <SafeAreaProvider>
70
+ <OxyBottomSheet {...bottomSheetProps} bottomSheetRef={internalBottomSheetRef} oxyServices={oxyServices} />
71
+ {children}
72
+ </SafeAreaProvider>
73
+ </BottomSheetModalProvider>
74
+ {/* Global Toaster for app-wide notifications outside of Modal contexts - only show if internal toaster is disabled */}
75
+ {!showInternalToaster && (
76
+ <View style={styles.toasterContainer}>
77
+ <Toaster position="top-center" swipeToDismissDirection="left" offset={15} />
78
+ </View>
79
+ )}
80
+ </GestureHandlerRootView>
81
+ </FontLoader>
48
82
  </OxyContextProvider>
49
- );
50
- }
51
-
52
- // Otherwise, provide both the context and the bottom sheet UI
53
- return (
54
- <OxyContextProvider
55
- oxyServices={oxyServices}
56
- storageKeyPrefix={storageKeyPrefix}
57
- onAuthStateChange={onAuthStateChange}
58
- bottomSheetRef={internalBottomSheetRef}
59
- >
60
- <FontLoader>
61
- <GestureHandlerRootView style={styles.gestureHandlerRoot}>
62
- <BottomSheetModalProvider>
63
- <StatusBar translucent backgroundColor="transparent" />
64
- <SafeAreaProvider>
65
- <OxyBottomSheet {...bottomSheetProps} bottomSheetRef={internalBottomSheetRef} oxyServices={oxyServices} />
66
- {children}
67
- </SafeAreaProvider>
68
- </BottomSheetModalProvider>
69
- {/* Global Toaster for app-wide notifications outside of Modal contexts - only show if internal toaster is disabled */}
70
- {!showInternalToaster && (
71
- <View style={styles.toasterContainer}>
72
- <Toaster position="top-center" swipeToDismissDirection="left" offset={15} />
73
- </View>
74
- )}
75
- </GestureHandlerRootView>
76
- </FontLoader>
77
- </OxyContextProvider>
83
+ </Provider>
78
84
  );
79
85
  };
80
86
 
@@ -97,7 +103,7 @@ const OxyBottomSheet: React.FC<OxyProviderProps> = ({
97
103
  }) => {
98
104
  // Use the internal ref (which is passed as a prop from OxyProvider)
99
105
  const modalRef = useRef<BottomSheetModalRef>(null);
100
-
106
+
101
107
  // Create a ref to store the navigation function from OxyRouter
102
108
  const navigationRef = useRef<((screen: string, props?: Record<string, any>) => void) | null>(null);
103
109
 
@@ -129,14 +135,14 @@ const OxyBottomSheet: React.FC<OxyProviderProps> = ({
129
135
  // @ts-ignore - Adding custom method
130
136
  bottomSheetRef.current._navigateToScreen = (screenName: string, props?: Record<string, any>) => {
131
137
  console.log(`Navigation requested: ${screenName}`, props);
132
-
138
+
133
139
  // Try direct navigation function first (most reliable)
134
140
  if (navigationRef.current) {
135
141
  console.log('Using direct navigation function');
136
142
  navigationRef.current(screenName, props);
137
143
  return;
138
144
  }
139
-
145
+
140
146
  // Fallback to event-based navigation
141
147
  if (typeof document !== 'undefined') {
142
148
  // For web - use a custom event
@@ -159,36 +165,24 @@ const OxyBottomSheet: React.FC<OxyProviderProps> = ({
159
165
  // Start with opacity 1 on Android to avoid visibility issues
160
166
  const fadeAnim = useRef(new Animated.Value(Platform.OS === 'android' ? 1 : 0)).current;
161
167
  const slideAnim = useRef(new Animated.Value(Platform.OS === 'android' ? 0 : 50)).current;
162
- const handleScaleAnim = useRef(new Animated.Value(1)).current;
163
168
 
164
169
  // Track keyboard status
165
170
  const [keyboardVisible, setKeyboardVisible] = useState(false);
166
- const [keyboardHeight, setKeyboardHeight] = useState(0);
167
171
  const insets = useSafeAreaInsets();
168
172
 
169
- // Get the authentication context
170
- const oxyContext = useOxy();
171
-
172
- // Handle keyboard events
173
+ // Handle keyboard events - memoized to prevent re-registration
173
174
  useEffect(() => {
174
175
  const keyboardWillShowListener = Keyboard.addListener(
175
176
  Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
176
177
  (event: KeyboardEvent) => {
177
- // Debounce rapid keyboard events
178
- if (!keyboardVisible) {
179
- setKeyboardVisible(true);
180
- // Get keyboard height from event
181
- const keyboardHeightValue = event.endCoordinates.height;
182
- setKeyboardHeight(keyboardHeightValue);
183
-
184
- // Ensure the bottom sheet remains visible when keyboard opens
185
- // by adjusting to the highest snap point
186
- if (modalRef.current) {
187
- // Use requestAnimationFrame to avoid conflicts
188
- requestAnimationFrame(() => {
189
- modalRef.current?.snapToIndex(1);
190
- });
191
- }
178
+ setKeyboardVisible(true);
179
+ // Ensure the bottom sheet remains visible when keyboard opens
180
+ // by adjusting to the highest snap point
181
+ if (modalRef.current) {
182
+ // Use requestAnimationFrame to avoid conflicts
183
+ requestAnimationFrame(() => {
184
+ modalRef.current?.snapToIndex(1);
185
+ });
192
186
  }
193
187
  }
194
188
  );
@@ -196,10 +190,7 @@ const OxyBottomSheet: React.FC<OxyProviderProps> = ({
196
190
  const keyboardWillHideListener = Keyboard.addListener(
197
191
  Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide',
198
192
  () => {
199
- if (keyboardVisible) {
200
- setKeyboardVisible(false);
201
- setKeyboardHeight(0);
202
- }
193
+ setKeyboardVisible(false);
203
194
  }
204
195
  );
205
196
 
@@ -208,7 +199,7 @@ const OxyBottomSheet: React.FC<OxyProviderProps> = ({
208
199
  keyboardWillShowListener.remove();
209
200
  keyboardWillHideListener.remove();
210
201
  };
211
- }, [keyboardVisible]);
202
+ }, []); // Remove keyboardVisible dependency to prevent re-registration
212
203
 
213
204
  // Present the modal when component mounts, but only if autoPresent is true
214
205
  useEffect(() => {
@@ -263,32 +254,48 @@ const OxyBottomSheet: React.FC<OxyProviderProps> = ({
263
254
  }
264
255
  }, [bottomSheetRef, modalRef, fadeAnim, slideAnim, autoPresent]);
265
256
 
257
+ // Close the bottom sheet with animation
258
+ const handleClose = useCallback(() => {
259
+ // Animate content out
260
+ Animated.timing(fadeAnim, {
261
+ toValue: 0,
262
+ duration: Platform.OS === 'android' ? 100 : 200, // Faster on Android
263
+ useNativeDriver: Platform.OS === 'ios', // Only use native driver on iOS
264
+ }).start(() => {
265
+ // Dismiss the sheet
266
+ modalRef.current?.dismiss();
267
+ if (onClose) {
268
+ setTimeout(() => {
269
+ onClose();
270
+ }, Platform.OS === 'android' ? 150 : 100);
271
+ }
272
+ });
273
+ }, [onClose, fadeAnim]);
274
+
266
275
  // Handle authentication success from the bottom sheet screens
267
276
  const handleAuthenticated = useCallback((user: any) => {
277
+ // Stop any ongoing animations that might interfere with dismissal
278
+ fadeAnim.stopAnimation();
279
+ slideAnim.stopAnimation();
280
+
268
281
  // Call the prop callback if provided
269
282
  if (onAuthenticated) {
270
283
  onAuthenticated(user);
271
284
  }
272
- }, [onAuthenticated]);
273
285
 
274
- // Handle indicator animation - subtle pulse effect
275
- useEffect(() => {
276
- const pulseAnimation = Animated.sequence([
277
- Animated.timing(handleScaleAnim, {
278
- toValue: 1.1,
279
- duration: 300,
280
- useNativeDriver: Platform.OS === 'ios', // Only use native driver on iOS
281
- }),
282
- Animated.timing(handleScaleAnim, {
283
- toValue: 1,
284
- duration: 300,
285
- useNativeDriver: Platform.OS === 'ios', // Only use native driver on iOS
286
- }),
287
- ]);
288
-
289
- // Run the animation once when component mounts
290
- pulseAnimation.start();
291
- }, []);
286
+ // Automatically dismiss the bottom sheet after successful authentication
287
+ // Use direct dismissal for immediate closure
288
+ modalRef.current?.dismiss();
289
+
290
+ // Call onClose callback if provided
291
+ if (onClose) {
292
+ setTimeout(() => {
293
+ onClose();
294
+ }, 100);
295
+ }
296
+ }, [onAuthenticated, onClose, fadeAnim, slideAnim]);
297
+
298
+ // Remove handle animation to prevent conflicts with bottom sheet animations
292
299
 
293
300
  // Handle backdrop rendering
294
301
  const renderBackdrop = useCallback(
@@ -319,76 +326,70 @@ const OxyBottomSheet: React.FC<OxyProviderProps> = ({
319
326
  };
320
327
  }, [customStyles.backgroundColor, theme]);
321
328
 
322
- // Method to adjust snap points from Router
329
+ // Method to adjust snap points from Router - memoized with stable dependencies
323
330
  const adjustSnapPoints = useCallback((points: string[]) => {
324
- // Ensure snap points are high enough when keyboard is visible
325
- if (keyboardVisible) {
326
- // If keyboard is visible, make sure we use higher snap points
327
- // to ensure the sheet content remains visible
328
- const highestPoint = points[points.length - 1];
329
- setSnapPoints([highestPoint, highestPoint]);
330
- } else {
331
- // If we have content height, use it as a constraint
332
- if (contentHeight > 0) {
333
- // Calculate content height as percentage of screen (plus some padding)
334
- // Clamp to ensure we don't exceed 90% to leave space for UI elements
335
- const contentHeightPercent = Math.min(Math.ceil((contentHeight) / screenHeight * 100), 90);
336
- const contentHeightPercentStr = `${contentHeightPercent}%`;
337
- // Use content height for first snap point if it's taller than the default
338
- const firstPoint = contentHeight / screenHeight > 0.6 ? contentHeightPercentStr : points[0];
339
- setSnapPoints([firstPoint, points[1] || '90%']);
331
+ setSnapPoints(prevSnapPoints => {
332
+ // Ensure snap points are high enough when keyboard is visible
333
+ if (keyboardVisible) {
334
+ // If keyboard is visible, make sure we use higher snap points
335
+ // to ensure the sheet content remains visible
336
+ const highestPoint = points[points.length - 1];
337
+ const newSnapPoints = [highestPoint, highestPoint];
338
+ // Only update if actually different
339
+ if (JSON.stringify(prevSnapPoints) !== JSON.stringify(newSnapPoints)) {
340
+ return newSnapPoints;
341
+ }
342
+ return prevSnapPoints;
340
343
  } else {
341
- setSnapPoints(points);
344
+ // If we have content height, use it as a constraint
345
+ if (contentHeight > 0) {
346
+ // Calculate content height as percentage of screen (plus some padding)
347
+ // Clamp to ensure we don't exceed 90% to leave space for UI elements
348
+ const contentHeightPercent = Math.min(Math.ceil((contentHeight) / screenHeight * 100), 90);
349
+ const contentHeightPercentStr = `${contentHeightPercent}%`;
350
+ // Use content height for first snap point if it's taller than the default
351
+ const firstPoint = contentHeight / screenHeight > 0.6 ? contentHeightPercentStr : points[0];
352
+ const newSnapPoints = [firstPoint, points[1] || '90%'];
353
+ // Only update if actually different
354
+ if (JSON.stringify(prevSnapPoints) !== JSON.stringify(newSnapPoints)) {
355
+ return newSnapPoints;
356
+ }
357
+ return prevSnapPoints;
358
+ } else {
359
+ // Only update if actually different
360
+ if (JSON.stringify(prevSnapPoints) !== JSON.stringify(points)) {
361
+ return points;
362
+ }
363
+ return prevSnapPoints;
364
+ }
342
365
  }
343
- }
366
+ });
344
367
  }, [keyboardVisible, contentHeight, screenHeight]);
345
368
 
346
- // Handle content layout changes to measure height and width
369
+ // Handle content layout changes to measure height and width - prevent rerender loops
347
370
  const handleContentLayout = useCallback((event: any) => {
348
371
  const { height: layoutHeight, width: layoutWidth } = event.nativeEvent.layout;
349
- setContentHeight(layoutHeight);
350
- setContainerWidth(layoutWidth);
351
-
352
- // Debug: log container dimensions
353
- console.log('[OxyProvider] Container dimensions (full):', { width: layoutWidth, height: layoutHeight });
354
-
355
- // Update snap points based on new content height
356
- if (keyboardVisible) {
357
- // If keyboard is visible, use the highest snap point
358
- const highestPoint = snapPoints[snapPoints.length - 1];
359
- setSnapPoints([highestPoint, highestPoint]);
360
- } else {
361
- if (layoutHeight > 0) {
362
- // Add padding and clamp to reasonable limits
363
- const contentHeightPercent = Math.min(Math.ceil((layoutHeight + 40) / screenHeight * 100), 90);
364
- const contentHeightPercentStr = `${contentHeightPercent}%`;
365
- const firstPoint = layoutHeight / screenHeight > 0.6 ? contentHeightPercentStr : snapPoints[0];
366
- setSnapPoints([firstPoint, snapPoints[1]]);
372
+
373
+ // Only update if values actually changed to prevent unnecessary rerenders
374
+ setContentHeight(prevHeight => {
375
+ if (Math.abs(prevHeight - layoutHeight) > 5) { // 5px threshold to prevent minor fluctuations
376
+ return layoutHeight;
367
377
  }
368
- }
369
- }, [keyboardVisible, screenHeight, snapPoints]);
378
+ return prevHeight;
379
+ });
370
380
 
371
- // Close the bottom sheet with animation
372
- const handleClose = useCallback(() => {
373
- // Animate content out
374
- Animated.timing(fadeAnim, {
375
- toValue: 0,
376
- duration: Platform.OS === 'android' ? 100 : 200, // Faster on Android
377
- useNativeDriver: Platform.OS === 'ios', // Only use native driver on iOS
378
- }).start(() => {
379
- // Dismiss the sheet
380
- modalRef.current?.dismiss();
381
- if (onClose) {
382
- setTimeout(() => {
383
- onClose();
384
- }, Platform.OS === 'android' ? 150 : 100);
381
+ setContainerWidth(prevWidth => {
382
+ if (Math.abs(prevWidth - layoutWidth) > 5) { // 5px threshold to prevent minor fluctuations
383
+ return layoutWidth;
385
384
  }
385
+ return prevWidth;
386
386
  });
387
- }, [onClose, fadeAnim]);
387
+ }, []); // Remove dependencies that cause rerender loops
388
388
 
389
- // Handle sheet index changes
389
+ // Handle sheet index changes - simplified to prevent unnecessary rerenders
390
390
  const handleSheetChanges = useCallback((index: number) => {
391
- }, [onClose, handleScaleAnim, keyboardVisible]);
391
+ // Sheet change handling can be added here if needed
392
+ }, []);
392
393
 
393
394
  return (
394
395
  <BottomSheetModal
@@ -88,10 +88,10 @@ export const OxySignInButton: React.FC<OxySignInButtonProps> = ({
88
88
  screen = 'SignIn',
89
89
  }) => {
90
90
  // Get all needed values from context in a single call
91
- const { user, showBottomSheet } = useOxy();
91
+ const { isAuthenticated, showBottomSheet } = useOxy();
92
92
 
93
93
  // Don't show the button if already authenticated (unless explicitly overridden)
94
- if (user && !showWhenAuthenticated) return null;
94
+ if (isAuthenticated && !showWhenAuthenticated) return null;
95
95
 
96
96
  // Default handler that uses the context methods
97
97
  const handlePress = () => {
@@ -0,0 +1,253 @@
1
+ import React from 'react';
2
+ import { View, TouchableOpacity, Text, ActivityIndicator, StyleSheet } from 'react-native';
3
+ import { Ionicons } from '@expo/vector-icons';
4
+
5
+ interface ButtonConfig {
6
+ text: string;
7
+ onPress: () => void;
8
+ icon?: string;
9
+ variant?: 'primary' | 'secondary' | 'transparent';
10
+ disabled?: boolean;
11
+ loading?: boolean;
12
+ testID?: string;
13
+ }
14
+
15
+ interface GroupedPillButtonsProps {
16
+ buttons: ButtonConfig[];
17
+ colors: any;
18
+ gap?: number;
19
+ }
20
+
21
+ const GroupedPillButtons: React.FC<GroupedPillButtonsProps> = ({
22
+ buttons,
23
+ colors,
24
+ gap = 8,
25
+ }) => {
26
+ const getButtonStyle = (button: ButtonConfig, index: number, totalButtons: number) => {
27
+ const baseStyle = {
28
+ flexDirection: 'row' as const,
29
+ alignItems: 'center' as const,
30
+ paddingVertical: 6,
31
+ paddingHorizontal: 12,
32
+ gap: 6,
33
+ minWidth: 70,
34
+ borderWidth: 1,
35
+ shadowOffset: {
36
+ width: 0,
37
+ height: 2,
38
+ },
39
+ shadowOpacity: 0.1,
40
+ shadowRadius: 4,
41
+ elevation: 2,
42
+ };
43
+
44
+ // Determine border radius based on position
45
+ let borderRadius = {
46
+ borderTopLeftRadius: 35,
47
+ borderBottomLeftRadius: 35,
48
+ borderTopRightRadius: 35,
49
+ borderBottomRightRadius: 35,
50
+ };
51
+
52
+ if (totalButtons > 1) {
53
+ if (index === 0) {
54
+ // First button
55
+ borderRadius = {
56
+ borderTopLeftRadius: 35,
57
+ borderBottomLeftRadius: 35,
58
+ borderTopRightRadius: 12,
59
+ borderBottomRightRadius: 12,
60
+ };
61
+ } else if (index === totalButtons - 1) {
62
+ // Last button
63
+ borderRadius = {
64
+ borderTopLeftRadius: 12,
65
+ borderBottomLeftRadius: 12,
66
+ borderTopRightRadius: 35,
67
+ borderBottomRightRadius: 35,
68
+ };
69
+ } else {
70
+ // Middle button (if 3 buttons)
71
+ borderRadius = {
72
+ borderTopLeftRadius: 12,
73
+ borderBottomLeftRadius: 12,
74
+ borderTopRightRadius: 12,
75
+ borderBottomRightRadius: 12,
76
+ };
77
+ }
78
+ }
79
+
80
+ // Determine colors based on variant
81
+ let backgroundColor = 'transparent';
82
+ let borderColor = colors.border;
83
+ let shadowColor = colors.border;
84
+ let textColor = colors.text;
85
+
86
+ switch (button.variant) {
87
+ case 'primary':
88
+ backgroundColor = colors.primary;
89
+ borderColor = colors.primary;
90
+ shadowColor = colors.primary;
91
+ textColor = '#FFFFFF';
92
+ break;
93
+ case 'secondary':
94
+ backgroundColor = colors.secondary || colors.primary;
95
+ borderColor = colors.secondary || colors.primary;
96
+ shadowColor = colors.secondary || colors.primary;
97
+ textColor = '#FFFFFF';
98
+ break;
99
+ case 'transparent':
100
+ default:
101
+ backgroundColor = 'transparent';
102
+ borderColor = colors.border;
103
+ shadowColor = colors.border;
104
+ textColor = colors.text;
105
+ break;
106
+ }
107
+
108
+ return {
109
+ ...baseStyle,
110
+ ...borderRadius,
111
+ backgroundColor,
112
+ borderColor,
113
+ shadowColor,
114
+ };
115
+ };
116
+
117
+ const getTextStyle = (button: ButtonConfig, colors: any) => {
118
+ const baseTextStyle = {
119
+ fontSize: 15,
120
+ fontWeight: '600' as const,
121
+ flex: 1,
122
+ };
123
+
124
+ let textColor = colors.text;
125
+ switch (button.variant) {
126
+ case 'primary':
127
+ case 'secondary':
128
+ textColor = '#FFFFFF';
129
+ break;
130
+ case 'transparent':
131
+ default:
132
+ textColor = colors.text;
133
+ break;
134
+ }
135
+
136
+ return {
137
+ ...baseTextStyle,
138
+ color: textColor,
139
+ };
140
+ };
141
+
142
+ const getIconColor = (button: ButtonConfig, colors: any) => {
143
+ switch (button.variant) {
144
+ case 'primary':
145
+ case 'secondary':
146
+ return '#FFFFFF';
147
+ case 'transparent':
148
+ default:
149
+ return colors.text;
150
+ }
151
+ };
152
+
153
+ const isBackButton = (button: ButtonConfig) => {
154
+ return button.text.toLowerCase().includes('back') ||
155
+ button.icon === 'arrow-back' ||
156
+ button.icon === 'chevron-back';
157
+ };
158
+
159
+ const renderButtonContent = (button: ButtonConfig, colors: any, index: number) => {
160
+ const iconColor = getIconColor(button, colors);
161
+ const isBack = isBackButton(button);
162
+ const isFirstButton = index === 0;
163
+ const isSingleButton = buttons.length === 1;
164
+
165
+ if (button.loading) {
166
+ return (
167
+ <ActivityIndicator
168
+ color={iconColor}
169
+ size="small"
170
+ />
171
+ );
172
+ }
173
+
174
+ // Auto-detect icon placement based on button order and type
175
+ if (isSingleButton) {
176
+ // Single button: icon on right
177
+ return (
178
+ <>
179
+ <Text style={getTextStyle(button, colors)}>
180
+ {button.text}
181
+ </Text>
182
+ {button.icon && (
183
+ <Ionicons
184
+ name={button.icon as any}
185
+ size={16}
186
+ color={iconColor}
187
+ />
188
+ )}
189
+ </>
190
+ );
191
+ } else if (isFirstButton || isBack) {
192
+ // First button or back button: icon on left, text on right
193
+ return (
194
+ <>
195
+ {button.icon && (
196
+ <Ionicons
197
+ name={button.icon as any}
198
+ size={16}
199
+ color={iconColor}
200
+ />
201
+ )}
202
+ <Text style={getTextStyle(button, colors)}>
203
+ {button.text}
204
+ </Text>
205
+ </>
206
+ );
207
+ } else {
208
+ // Second button or forward/action button: text on left, icon on right
209
+ return (
210
+ <>
211
+ <Text style={getTextStyle(button, colors)}>
212
+ {button.text}
213
+ </Text>
214
+ {button.icon && (
215
+ <Ionicons
216
+ name={button.icon as any}
217
+ size={16}
218
+ color={iconColor}
219
+ />
220
+ )}
221
+ </>
222
+ );
223
+ }
224
+ };
225
+
226
+ return (
227
+ <View style={[styles.container, { gap }]}>
228
+ {buttons.map((button, index) => (
229
+ <TouchableOpacity
230
+ key={index}
231
+ style={getButtonStyle(button, index, buttons.length)}
232
+ onPress={button.onPress}
233
+ disabled={button.disabled || button.loading}
234
+ testID={button.testID}
235
+ >
236
+ {renderButtonContent(button, colors, index)}
237
+ </TouchableOpacity>
238
+ ))}
239
+ </View>
240
+ );
241
+ };
242
+
243
+ const styles = StyleSheet.create({
244
+ container: {
245
+ flexDirection: 'row',
246
+ justifyContent: 'center',
247
+ marginTop: 16,
248
+ marginBottom: 8,
249
+ width: '100%',
250
+ },
251
+ });
252
+
253
+ export default GroupedPillButtons;