@oxyhq/services 5.4.3 → 5.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/lib/commonjs/assets/assets/illustrations/HighFive.tsx +41 -0
- package/lib/commonjs/assets/icons/OxyServices.js +1 -1
- package/lib/commonjs/assets/illustrations/HighFive.js +61 -0
- package/lib/commonjs/assets/illustrations/HighFive.js.map +1 -0
- package/lib/commonjs/core/index.js +2 -2
- package/lib/commonjs/index.js +22 -22
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/node/createAuth.js +95 -0
- package/lib/commonjs/node/createAuth.js.map +1 -0
- package/lib/commonjs/node/index.js +15 -6
- package/lib/commonjs/node/index.js.map +1 -1
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/ui/components/Avatar.js +3 -3
- package/lib/commonjs/ui/components/Avatar.js.map +1 -1
- package/lib/commonjs/ui/components/FollowButton.js +3 -3
- package/lib/commonjs/ui/components/GroupedSection.js +1 -1
- package/lib/commonjs/ui/components/OxyLogo.js +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +146 -141
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/OxySignInButton.js +2 -2
- package/lib/commonjs/ui/components/ProfileCard.js +2 -2
- package/lib/commonjs/ui/components/Section.js +1 -1
- package/lib/commonjs/ui/components/SectionTitle.js +1 -1
- package/lib/commonjs/ui/components/icon/index.js +1 -1
- package/lib/commonjs/ui/components/index.js +12 -12
- package/lib/commonjs/ui/components/internal/GroupedPillButtons.js +213 -0
- package/lib/commonjs/ui/components/internal/GroupedPillButtons.js.map +1 -0
- package/lib/commonjs/ui/components/internal/TextField.js +576 -0
- package/lib/commonjs/ui/components/internal/TextField.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +1 -1
- package/lib/commonjs/ui/index.js +19 -11
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/navigation/OxyRouter.js +23 -18
- package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountCenterScreen.js +18 -18
- package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountManagementDemo.js +3 -3
- package/lib/commonjs/ui/screens/AccountManagementDemo.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js +4 -4
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +5 -5
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +3 -3
- package/lib/commonjs/ui/screens/AppInfoScreen.js +6 -6
- package/lib/commonjs/ui/screens/BillingManagementScreen.js +3 -3
- package/lib/commonjs/ui/screens/FeedbackScreen.js +1169 -0
- package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/FileManagementScreen.js +3 -3
- package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js +3 -3
- package/lib/commonjs/ui/screens/ProfileScreen.js +2 -2
- package/lib/commonjs/ui/screens/SessionManagementScreen.js +2 -2
- package/lib/commonjs/ui/screens/SignInScreen.js +182 -304
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignUpScreen.js +811 -712
- package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +3 -3
- package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +2 -2
- package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +1 -1
- package/lib/commonjs/ui/store/index.js +52 -0
- package/lib/commonjs/ui/store/index.js.map +1 -0
- package/lib/commonjs/ui/styles/index.js +2 -2
- package/lib/commonjs/ui/styles/theme.js +1 -1
- package/lib/commonjs/utils/index.js +1 -1
- package/lib/module/assets/assets/illustrations/HighFive.tsx +41 -0
- package/lib/module/assets/icons/OxyServices.js +1 -1
- package/lib/module/assets/icons/OxyServices.js.map +1 -1
- package/lib/module/assets/illustrations/HighFive.js +55 -0
- package/lib/module/assets/illustrations/HighFive.js.map +1 -0
- package/lib/module/core/index.js +2 -2
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/index.js +10 -10
- package/lib/module/index.js.map +1 -1
- package/lib/module/node/createAuth.js +90 -0
- package/lib/module/node/createAuth.js.map +1 -0
- package/lib/module/node/index.js +8 -4
- package/lib/module/node/index.js.map +1 -1
- package/lib/module/package.json +1 -0
- package/lib/module/ui/components/Avatar.js +2 -2
- package/lib/module/ui/components/Avatar.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +3 -3
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/GroupedSection.js +1 -1
- package/lib/module/ui/components/GroupedSection.js.map +1 -1
- package/lib/module/ui/components/OxyLogo.js +1 -1
- package/lib/module/ui/components/OxyLogo.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +143 -138
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/OxySignInButton.js +2 -2
- package/lib/module/ui/components/OxySignInButton.js.map +1 -1
- package/lib/module/ui/components/ProfileCard.js +2 -2
- package/lib/module/ui/components/ProfileCard.js.map +1 -1
- package/lib/module/ui/components/Section.js +1 -1
- package/lib/module/ui/components/Section.js.map +1 -1
- package/lib/module/ui/components/SectionTitle.js +1 -1
- package/lib/module/ui/components/SectionTitle.js.map +1 -1
- package/lib/module/ui/components/icon/index.js +1 -1
- package/lib/module/ui/components/icon/index.js.map +1 -1
- package/lib/module/ui/components/index.js +12 -12
- package/lib/module/ui/components/index.js.map +1 -1
- package/lib/module/ui/components/internal/GroupedPillButtons.js +208 -0
- package/lib/module/ui/components/internal/GroupedPillButtons.js.map +1 -0
- package/lib/module/ui/components/internal/TextField.js +571 -0
- package/lib/module/ui/components/internal/TextField.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +1 -1
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/index.js +12 -10
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/navigation/OxyRouter.js +23 -18
- package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/module/ui/screens/AccountCenterScreen.js +5 -5
- package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountManagementDemo.js +2 -2
- package/lib/module/ui/screens/AccountManagementDemo.js.map +1 -1
- package/lib/module/ui/screens/AccountOverviewScreen.js +4 -4
- package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +5 -5
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +3 -3
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/AppInfoScreen.js +6 -6
- package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/module/ui/screens/BillingManagementScreen.js +3 -3
- package/lib/module/ui/screens/BillingManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/FeedbackScreen.js +1164 -0
- package/lib/module/ui/screens/FeedbackScreen.js.map +1 -0
- package/lib/module/ui/screens/FileManagementScreen.js +3 -3
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/PremiumSubscriptionScreen.js +3 -3
- package/lib/module/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +2 -2
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/SessionManagementScreen.js +2 -2
- package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +182 -304
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/SignUpScreen.js +810 -712
- package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js +3 -3
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +2 -2
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js +1 -1
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
- package/lib/module/ui/store/index.js +44 -0
- package/lib/module/ui/store/index.js.map +1 -0
- package/lib/module/ui/styles/index.js +2 -2
- package/lib/module/ui/styles/index.js.map +1 -1
- package/lib/module/ui/styles/theme.js +1 -1
- package/lib/module/ui/styles/theme.js.map +1 -1
- package/lib/module/utils/index.js +1 -1
- package/lib/module/utils/index.js.map +1 -1
- package/lib/typescript/assets/illustrations/HighFive.d.ts +9 -0
- package/lib/typescript/assets/illustrations/HighFive.d.ts.map +1 -0
- package/lib/typescript/node/createAuth.d.ts +7 -0
- package/lib/typescript/node/createAuth.d.ts.map +1 -0
- package/lib/typescript/node/index.d.ts +2 -0
- package/lib/typescript/node/index.d.ts.map +1 -1
- package/lib/typescript/types/expo-vector-icons.d.ts +3 -0
- package/lib/typescript/types/express.d.ts +5 -0
- package/lib/typescript/types/react-redux.d.ts +5 -0
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts +18 -0
- package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts.map +1 -0
- package/lib/typescript/ui/components/internal/TextField.d.ts +25 -0
- package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -0
- package/lib/typescript/ui/index.d.ts +2 -0
- package/lib/typescript/ui/index.d.ts.map +1 -1
- package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
- package/lib/typescript/ui/screens/FeedbackScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
- package/lib/typescript/ui/store/index.d.ts +19 -0
- package/lib/typescript/ui/store/index.d.ts.map +1 -0
- package/package.json +10 -25
- package/src/assets/illustrations/HighFive.tsx +41 -0
- package/src/node/createAuth.ts +116 -0
- package/src/node/index.ts +4 -0
- package/src/types/expo-vector-icons.d.ts +3 -0
- package/src/types/express.d.ts +5 -0
- package/src/types/react-redux.d.ts +5 -0
- package/src/ui/components/OxyProvider.tsx +136 -135
- package/src/ui/components/internal/GroupedPillButtons.tsx +253 -0
- package/src/ui/components/internal/TextField.tsx +694 -0
- package/src/ui/index.ts +6 -2
- package/src/ui/navigation/OxyRouter.tsx +8 -3
- package/src/ui/screens/FeedbackScreen.tsx +1042 -0
- package/src/ui/screens/SignInScreen.tsx +179 -222
- package/src/ui/screens/SignUpScreen.tsx +772 -608
- package/src/ui/store/index.ts +51 -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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
378
|
+
return prevHeight;
|
|
379
|
+
});
|
|
370
380
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
391
|
+
// Sheet change handling can be added here if needed
|
|
392
|
+
}, []);
|
|
392
393
|
|
|
393
394
|
return (
|
|
394
395
|
<BottomSheetModal
|
|
@@ -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;
|