@oxyhq/services 5.21.5 → 5.21.6

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 (49) hide show
  1. package/lib/commonjs/ui/components/BottomSheetRouter.js +100 -286
  2. package/lib/commonjs/ui/components/BottomSheetRouter.js.map +1 -1
  3. package/lib/commonjs/ui/components/GroupedItem.js +0 -3
  4. package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
  5. package/lib/commonjs/ui/components/OxyProvider.js +14 -19
  6. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  7. package/lib/commonjs/ui/navigation/bottomSheetManager.js +43 -145
  8. package/lib/commonjs/ui/navigation/bottomSheetManager.js.map +1 -1
  9. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +0 -2
  10. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  11. package/lib/module/ui/components/BottomSheetRouter.js +102 -284
  12. package/lib/module/ui/components/BottomSheetRouter.js.map +1 -1
  13. package/lib/module/ui/components/GroupedItem.js +0 -3
  14. package/lib/module/ui/components/GroupedItem.js.map +1 -1
  15. package/lib/module/ui/components/OxyProvider.js +14 -19
  16. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  17. package/lib/module/ui/navigation/bottomSheetManager.js +37 -135
  18. package/lib/module/ui/navigation/bottomSheetManager.js.map +1 -1
  19. package/lib/module/ui/screens/AccountSettingsScreen.js +0 -2
  20. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  21. package/lib/typescript/commonjs/ui/components/BottomSheetRouter.d.ts +2 -7
  22. package/lib/typescript/commonjs/ui/components/BottomSheetRouter.d.ts.map +1 -1
  23. package/lib/typescript/commonjs/ui/components/GroupedItem.d.ts.map +1 -1
  24. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  25. package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts +11 -60
  26. package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts.map +1 -1
  27. package/lib/typescript/commonjs/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  28. package/lib/typescript/module/ui/components/BottomSheetRouter.d.ts +2 -7
  29. package/lib/typescript/module/ui/components/BottomSheetRouter.d.ts.map +1 -1
  30. package/lib/typescript/module/ui/components/GroupedItem.d.ts.map +1 -1
  31. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  32. package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts +11 -60
  33. package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts.map +1 -1
  34. package/lib/typescript/module/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  35. package/package.json +2 -2
  36. package/src/ui/components/BottomSheetRouter.tsx +97 -319
  37. package/src/ui/components/GroupedItem.tsx +0 -4
  38. package/src/ui/components/OxyProvider.tsx +13 -18
  39. package/src/ui/navigation/bottomSheetManager.ts +43 -150
  40. package/src/ui/screens/AccountSettingsScreen.tsx +0 -2
  41. package/lib/commonjs/ui/hooks/use-haptic-press.js +0 -21
  42. package/lib/commonjs/ui/hooks/use-haptic-press.js.map +0 -1
  43. package/lib/module/ui/hooks/use-haptic-press.js +0 -17
  44. package/lib/module/ui/hooks/use-haptic-press.js.map +0 -1
  45. package/lib/typescript/commonjs/ui/hooks/use-haptic-press.d.ts +0 -8
  46. package/lib/typescript/commonjs/ui/hooks/use-haptic-press.d.ts.map +0 -1
  47. package/lib/typescript/module/ui/hooks/use-haptic-press.d.ts +0 -8
  48. package/lib/typescript/module/ui/hooks/use-haptic-press.d.ts.map +0 -1
  49. package/src/ui/hooks/use-haptic-press.ts +0 -15
@@ -1,74 +1,25 @@
1
1
  import type { RouteName } from './routes';
2
2
  /**
3
- * Bottom Sheet Manager - Pure state management module
4
- *
5
- * Uses Zustand (vanilla) for robust state management without React dependencies.
6
- * This ensures the manager can be imported safely anywhere.
3
+ * Bottom Sheet State Manager
7
4
  */
8
- export interface NavigationHistoryEntry {
9
- screen: RouteName;
10
- props: Record<string, unknown>;
11
- step?: number;
12
- }
13
- export interface BottomSheetRouterState {
5
+ export interface BottomSheetState {
14
6
  currentScreen: RouteName | null;
15
7
  screenProps: Record<string, unknown>;
16
8
  currentStep?: number;
17
- navigationHistory: NavigationHistoryEntry[];
9
+ history: Array<{
10
+ screen: RouteName;
11
+ props: Record<string, unknown>;
12
+ step?: number;
13
+ }>;
18
14
  isOpen: boolean;
19
15
  }
20
- export declare const bottomSheetStore: import("zustand/vanilla").StoreApi<BottomSheetRouterState>;
21
- type BottomSheetRefObject = {
22
- current: {
23
- present: () => void;
24
- dismiss: () => void;
25
- } | null;
26
- } | null;
27
- /**
28
- * Set the bottom sheet ref so showBottomSheet can control it
29
- */
30
- export declare const setBottomSheetRef: (ref: BottomSheetRefObject) => void;
31
- /**
32
- * Update the bottom sheet state
33
- * (Kept for backward compatibility, but prefer using store directly if possible)
34
- */
35
- export declare const updateBottomSheetState: (updates: Partial<BottomSheetRouterState>) => void;
36
- /**
37
- * Subscribe to bottom sheet state changes
38
- * (Wrapper around store.subscribe for backward compatibility)
39
- */
40
- export declare const subscribeToBottomSheetState: (listener: (state: BottomSheetRouterState) => void) => () => void;
41
- /**
42
- * Get the current bottom sheet state
43
- * (Wrapper around store.getState for backward compatibility)
44
- */
45
- export declare const getBottomSheetState: () => BottomSheetRouterState;
46
- /**
47
- * Show the bottom sheet with a specific screen (internal - no route validation)
48
- */
49
- export declare const managerShowBottomSheet: (screen: RouteName, props?: Record<string, unknown>, options?: {
50
- addToHistory?: boolean;
51
- step?: number;
52
- }) => void;
53
- /**
54
- * Close the bottom sheet (internal)
55
- */
56
- export declare const managerCloseBottomSheet: () => void;
57
- /**
58
- * Go back in navigation history
59
- * Returns true if back navigation was successful, false if history is empty
60
- */
61
- export declare const managerGoBack: () => boolean;
62
- /**
63
- * Public API for showing bottom sheets
64
- */
16
+ export declare const bottomSheetStore: import("zustand/vanilla").StoreApi<BottomSheetState>;
17
+ export declare const getState: () => BottomSheetState;
65
18
  export declare const showBottomSheet: (screenOrConfig: RouteName | {
66
19
  screen: RouteName;
67
20
  props?: Record<string, unknown>;
68
21
  }) => void;
69
- /**
70
- * Public API for closing bottom sheets
71
- */
72
22
  export declare const closeBottomSheet: () => void;
73
- export {};
23
+ export declare const goBack: () => boolean;
24
+ export declare const updateState: (updates: Partial<BottomSheetState>) => void;
74
25
  //# sourceMappingURL=bottomSheetManager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bottomSheetManager.d.ts","sourceRoot":"","sources":["../../../../../src/ui/navigation/bottomSheetManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAI1C;;;;;GAKG;AAEH,MAAM,WAAW,sBAAsB;IACnC,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACnC,aAAa,EAAE,SAAS,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,sBAAsB,EAAE,CAAC;IAC5C,MAAM,EAAE,OAAO,CAAC;CACnB;AAYD,eAAO,MAAM,gBAAgB,4DAA0D,CAAC;AAGxF,KAAK,oBAAoB,GAAG;IAAE,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,IAAI,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAAC;AAGpG;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,oBAAoB,SAE1D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,OAAO,CAAC,sBAAsB,CAAC,SAE9E,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,2BAA2B,GAAI,UAAU,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,eAE5F,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,QAAO,sBAEtC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,GAC/B,QAAQ,SAAS,EACjB,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,UAAU;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KACpD,IAiCF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,QAAO,IAY1C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,QAAO,OAoBhC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GACxB,gBAAgB,SAAS,GAAG;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,KACnF,IAmBF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,QAAO,IAEnC,CAAC"}
1
+ {"version":3,"file":"bottomSheetManager.d.ts","sourceRoot":"","sources":["../../../../../src/ui/navigation/bottomSheetManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAI1C;;GAEG;AAEH,MAAM,WAAW,gBAAgB;IAC7B,aAAa,EAAE,SAAS,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrF,MAAM,EAAE,OAAO,CAAC;CACnB;AAUD,eAAO,MAAM,gBAAgB,sDAAoD,CAAC;AAElF,eAAO,MAAM,QAAQ,wBAAoC,CAAC;AAE1D,eAAO,MAAM,eAAe,GACxB,gBAAgB,SAAS,GAAG;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,KACnF,IA4BF,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAO,IAEnC,CAAC;AAEF,eAAO,MAAM,MAAM,QAAO,OAezB,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,SAAS,OAAO,CAAC,gBAAgB,CAAC,SAE7D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"AccountSettingsScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/AccountSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAcjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;;mBA8Cc,MAAM;qBAAmB,MAAM;;AAsgExG,wBAAiD"}
1
+ {"version":3,"file":"AccountSettingsScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/AccountSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAcjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;;mBA6Cc,MAAM;qBAAmB,MAAM;;AAqgExG,wBAAiD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "5.21.5",
3
+ "version": "5.21.6",
4
4
  "description": "Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -252,4 +252,4 @@
252
252
  "react-native-gesture-handler": "~2.28.0",
253
253
  "react-native-reanimated": "~4.1.1"
254
254
  }
255
- }
255
+ }
@@ -1,12 +1,5 @@
1
- import React, { useRef, useEffect, useCallback } from 'react';
1
+ import React, { useRef, useEffect, useCallback, useMemo } from 'react';
2
2
  import { BackHandler, View, StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
3
- import Animated, {
4
- useSharedValue,
5
- useAnimatedStyle,
6
- withTiming,
7
- runOnJS,
8
- type SharedValue,
9
- } from 'react-native-reanimated';
10
3
  import { useStore } from 'zustand';
11
4
  import type { RouteName } from '../navigation/routes';
12
5
  import { getScreenComponent, isValidRoute } from '../navigation/routes';
@@ -15,15 +8,12 @@ import { useColorScheme } from '../hooks/use-color-scheme';
15
8
  import { Colors } from '../constants/theme';
16
9
  import BottomSheet, { type BottomSheetRef } from './BottomSheet';
17
10
  import {
18
- setBottomSheetRef as setManagerRef,
19
- updateBottomSheetState,
20
- subscribeToBottomSheetState,
21
- managerShowBottomSheet,
22
- managerCloseBottomSheet,
23
- managerGoBack,
24
- type BottomSheetRouterState,
25
- getBottomSheetState,
26
11
  bottomSheetStore,
12
+ getState,
13
+ showBottomSheet,
14
+ closeBottomSheet,
15
+ goBack,
16
+ updateState,
27
17
  } from '../navigation/bottomSheetManager';
28
18
 
29
19
  export interface BottomSheetRouterProps {
@@ -31,365 +21,153 @@ export interface BottomSheetRouterProps {
31
21
  onDismiss?: () => void;
32
22
  }
33
23
 
34
- // Re-export types for backward compatibility
35
- export type { BottomSheetRouterState };
36
- export { subscribeToBottomSheetState };
37
-
38
- // Re-export BottomSheetRef for backward compatibility
39
- export type { BottomSheetRef };
40
-
41
24
  /**
42
- * BottomSheetRouter - Manages navigation within bottom sheet modals
43
- *
44
- * Uses custom BottomSheet component built with react-native-reanimated v4.
45
- * State is managed by bottomSheetManager (single source of truth).
46
- *
47
- * Features:
48
- * - Screen navigation with history stack
49
- * - Step-based navigation for multi-step screens
50
- * - Android back button handling
51
- * - Minimal props passing (screens use useOxy() for context)
25
+ * BottomSheetRouter - Navigation container for bottom sheet screens
52
26
  */
53
- // Animated screen container for smooth transitions
54
- const AnimatedScreenContainer: React.FC<{
55
- children: React.ReactNode;
56
- fadeAnim: SharedValue<number>;
57
- scaleAnim: SharedValue<number>;
58
- screenKey: string;
59
- }> = ({ children, fadeAnim, scaleAnim, screenKey }) => {
60
- const animatedStyle = useAnimatedStyle(() => ({
61
- opacity: fadeAnim.value,
62
- transform: [
63
- { scale: scaleAnim.value }
64
- ]
65
- }));
66
-
67
- return (
68
- <Animated.View
69
- key={screenKey}
70
- style={[{ flexShrink: 1 }, animatedStyle]}
71
- >
72
- {children}
73
- </Animated.View>
74
- );
75
- };
76
-
77
- const BottomSheetRouterComponent: React.FC<BottomSheetRouterProps> = ({ onScreenChange, onDismiss }) => {
78
- const bottomSheetRef = useRef<BottomSheetRef>(null);
27
+ const BottomSheetRouter: React.FC<BottomSheetRouterProps> = ({ onScreenChange, onDismiss }) => {
28
+ const sheetRef = useRef<BottomSheetRef>(null);
79
29
  const colorScheme = useColorScheme();
80
30
  const colors = Colors[colorScheme ?? 'light'];
31
+ const prevScreenRef = useRef<RouteName | null>(null);
81
32
 
82
- // Animation values for screen transitions
83
- const fadeAnim = useSharedValue(1);
84
- const scaleAnim = useSharedValue(1);
85
- const previousScreenRef = useRef<RouteName | null>(null);
86
- const isTransitioningRef = useRef(false);
87
-
88
- // Create stable ref object for manager compatibility
89
- // Manager expects: { current: { present: () => void; dismiss: () => void } | null } | null
90
- const managerRefObject = useRef({
91
- current: {
92
- present: () => bottomSheetRef.current?.present(),
93
- dismiss: () => bottomSheetRef.current?.dismiss(),
94
- },
95
- });
96
-
97
- // Single source of truth - subscribe to manager state using Zustand
98
- const state = useStore(bottomSheetStore);
33
+ const { currentScreen, screenProps, currentStep, isOpen } = useStore(bottomSheetStore);
99
34
 
100
- // Animate screen transition when screen changes
101
- const animateScreenTransition = useCallback((newScreen: RouteName | null, newState: BottomSheetRouterState) => {
102
- // ... implementation uses newState which comes from the store ...
103
- // We can just rely on the re-render from useStore, no need to manually set local state
104
- // However, the animation logic relied on queueing state updates.
105
- // Let's adapt it.
106
- onScreenChange?.(newScreen);
107
-
108
- // Note: The original logic tried to defer the state update (setState) until animation completed.
109
- // With Zustand, the global state is already updated.
110
- // The animation logic needs to react to the state change.
111
-
112
- // Since useStore forces a re-render with the NEW state, we are already "in" the new state.
113
- // To animate "out" the old screen, we would need to have captured the previous screen
114
- // before the re-render, OR we accept that the transition might be slightly different.
115
-
116
- // Actually, the original logic had `setState` to control when the UI updates.
117
- // With global store, the UI updates immediately.
118
- // For now, let's trust the re-render. If animation is jerky, we can revisit.
119
- // But wait, the original logic:
120
- // 1. Check if transitioning. If so, wait.
121
- // 2. If valid change, start exit animation -> then update local state -> then enter animation.
122
-
123
- // With Zustand, we can't "delay" the state update because it's global.
124
- // So `state` (from useStore) is ALREADY the new state.
125
- // We need to detect that `state.currentScreen` changed from our `previousScreenRef`.
126
-
127
- }, [onScreenChange]);
35
+ const ScreenComponent = useMemo(
36
+ () => (currentScreen ? getScreenComponent(currentScreen) : null),
37
+ [currentScreen]
38
+ );
128
39
 
129
- // Handle screen transitions based on state changes
40
+ // Notify screen changes
130
41
  useEffect(() => {
131
- const newScreen = state.currentScreen;
132
- const previousScreen = previousScreenRef.current;
133
- const isScreenChange = previousScreen !== null && previousScreen !== newScreen && newScreen !== null;
134
-
135
- if (isScreenChange) {
136
- // Logic to handle transition...
137
- // Since we are already re-rendered with new state, we might see a flash of new content.
138
- // Ideally we shouldn't have updated the global store until animation started...
139
- // BUT `bottomSheetManager` updates the store immediately.
42
+ if (prevScreenRef.current !== currentScreen) {
43
+ onScreenChange?.(currentScreen);
44
+ prevScreenRef.current = currentScreen;
45
+ }
46
+ }, [currentScreen, onScreenChange]);
140
47
 
141
- // For this fix, let's keep it simple: Just update the refs.
142
- // The complex animation logic in the original file was trying to bridge imperative calls with React state.
48
+ // Control visibility
49
+ useEffect(() => {
50
+ if (!sheetRef.current) return;
143
51
 
144
- previousScreenRef.current = newScreen;
145
- } else if (previousScreen === null && newScreen !== null) {
146
- // Opening first time
147
- fadeAnim.value = 1;
148
- scaleAnim.value = 1;
149
- previousScreenRef.current = newScreen;
150
- onScreenChange?.(newScreen);
52
+ if (isOpen) {
53
+ sheetRef.current.present();
151
54
  } else {
152
- previousScreenRef.current = newScreen;
55
+ sheetRef.current.dismiss();
153
56
  }
57
+ }, [isOpen]);
154
58
 
155
- setManagerRef(managerRefObject.current);
156
- }, [state.currentScreen, onScreenChange, fadeAnim, scaleAnim]);
157
-
158
- // Clean up on unmount
59
+ // Android back button
159
60
  useEffect(() => {
160
- return () => {
161
- setManagerRef(null);
162
- };
163
- }, []);
164
-
165
- // Handle explicit dismiss - only update state, don't call dismiss again
166
- const handleDismiss = useCallback(() => {
167
- // Only update state, don't call dismiss() as it's already being dismissed
168
- updateBottomSheetState({
169
- currentScreen: null,
170
- screenProps: {},
171
- currentStep: undefined,
172
- navigationHistory: [],
173
- isOpen: false,
61
+ if (!isOpen) return;
62
+ const handler = BackHandler.addEventListener('hardwareBackPress', () => {
63
+ handleGoBack();
64
+ return true;
174
65
  });
175
- onDismiss?.();
176
- }, [onDismiss]);
177
-
178
- // Get current screen component (lazy-loaded)
179
- const ScreenComponent = state.currentScreen ? getScreenComponent(state.currentScreen) : null;
66
+ return () => handler.remove();
67
+ }, [isOpen]);
180
68
 
181
- // Navigation handler - validates route and updates manager state
182
- // For step-based screens, step changes are treated as navigation events (added to history)
183
69
  const navigate = useCallback((screen: RouteName, props?: Record<string, unknown>) => {
184
70
  if (!isValidRoute(screen)) {
185
- if (__DEV__) {
186
- console.warn(`[BottomSheetRouter] Invalid route: ${screen}`);
187
- }
71
+ if (__DEV__) console.warn(`[BottomSheetRouter] Invalid route: ${screen}`);
188
72
  return;
189
73
  }
190
-
191
- const isSameScreen = screen === state.currentScreen;
192
- const newStep = typeof props?.initialStep === 'number' ? props.initialStep : undefined;
193
- const currentStep = state.currentStep ?? (typeof state.screenProps?.initialStep === 'number' ? state.screenProps.initialStep : undefined);
194
-
195
- // For step-based screens: if same screen but different step, treat as navigation (add to history)
196
- // This allows step navigation to be handled by router with animations
197
- const isStepChange = isSameScreen && newStep !== undefined && newStep !== currentStep;
198
-
199
- managerShowBottomSheet(screen, props, {
200
- addToHistory: !isSameScreen || isStepChange, // Add to history if different screen OR step change
201
- });
202
- }, [state.currentScreen, state.currentStep, state.screenProps]);
203
-
204
- // Step change handler for step-based screens
205
- // This is called by StepBasedScreen to notify router of step changes
206
- // The router now handles step navigation through navigate/goBack, so this is mainly for compatibility
207
- const handleStepChange = useCallback((step: number, _totalSteps?: number) => {
208
- // Step changes are now handled through navigate/goBack, but we still update currentStep
209
- // for screens that might query it directly
210
- if (state.currentScreen) {
211
- updateBottomSheetState({
212
- currentStep: step,
213
- screenProps: {
214
- ...state.screenProps,
215
- initialStep: step,
216
- }
217
- });
218
- }
219
- }, [state.currentScreen, state.screenProps]);
220
-
221
- // Check if the bottom sheet can be dismissed (no navigation history or steps to go back to)
222
- const canDismiss = useCallback((): boolean => {
223
- // Use global state to avoid stale closures during transitions
224
- const currentState = getBottomSheetState();
225
-
226
- // Check if there's navigation history
227
- if (currentState.navigationHistory.length > 0) {
228
- return false;
229
- }
230
-
231
- // Check if there are steps to go back to
232
- const initialStep = typeof currentState.screenProps?.initialStep === 'number'
233
- ? currentState.screenProps.initialStep
234
- : undefined;
235
- const currentStep = currentState.currentStep ?? initialStep ?? 0;
236
- const isStepBased = initialStep !== undefined || currentState.currentStep !== undefined;
237
-
238
- if (isStepBased && typeof currentStep === 'number' && currentStep > 0) {
239
- return false;
240
- }
241
-
242
- // No history and on step 0 (or not step-based) - can dismiss
243
- return true;
74
+ showBottomSheet({ screen, props });
244
75
  }, []);
245
76
 
246
- // Go back handler with priority:
247
- // 1. Screen history (navigate to previous screen)
248
- // 2. Step navigation (navigate to previous step)
249
- // 3. Close sheet (no history/step available)
250
- const goBack = useCallback(() => {
251
- // Use global state to avoid stale closures during transitions
252
- const currentState = getBottomSheetState();
77
+ const handleGoBack = useCallback(() => {
78
+ const state = getState();
253
79
 
254
- // Priority 1: Screen history
255
- if (currentState.navigationHistory.length > 0) {
256
- const wentBack = managerGoBack();
257
- if (wentBack) {
258
- return true;
259
- }
80
+ // Try history first
81
+ if (state.history.length > 0) {
82
+ goBack();
83
+ return true;
260
84
  }
261
85
 
262
- // Priority 2: Step navigation
263
- const initialStep = typeof currentState.screenProps?.initialStep === 'number'
264
- ? currentState.screenProps.initialStep
265
- : undefined;
266
- const currentStep = currentState.currentStep ?? initialStep ?? 0;
267
- const isStepBased = initialStep !== undefined || currentState.currentStep !== undefined;
268
-
269
- if (isStepBased && typeof currentStep === 'number' && currentStep > 0) {
270
- const previousStep = currentStep - 1;
271
- updateBottomSheetState({
272
- screenProps: {
273
- ...currentState.screenProps,
274
- initialStep: previousStep,
275
- },
276
- currentStep: previousStep,
86
+ // Try step back
87
+ const step = state.currentStep ?? 0;
88
+ if (step > 0) {
89
+ updateState({
90
+ currentStep: step - 1,
91
+ screenProps: { ...state.screenProps, initialStep: step - 1 },
277
92
  });
278
93
  return true;
279
94
  }
280
95
 
281
- // Priority 3: Close sheet
282
- managerCloseBottomSheet();
96
+ // Close
97
+ closeBottomSheet();
283
98
  return true;
284
99
  }, []);
285
100
 
286
- // Android hardware back button handler
287
- useEffect(() => {
288
- if (!state.isOpen) {
289
- return;
290
- }
291
-
292
- const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
293
- return goBack();
294
- });
295
-
296
- return () => {
297
- backHandler.remove();
298
- };
299
- }, [state.isOpen, goBack]);
101
+ const canDismiss = useCallback((): boolean => {
102
+ const state = getState();
103
+ if (state.history.length > 0) return false;
104
+ const step = state.currentStep ?? 0;
105
+ return step <= 0;
106
+ }, []);
300
107
 
301
- // Present modal when state changes to open
302
- // This must be before any early returns to follow rules of hooks
303
- useEffect(() => {
304
- if (state.isOpen && bottomSheetRef.current) {
305
- bottomSheetRef.current.present();
108
+ const handleDismissAttempt = useCallback((): boolean => {
109
+ if (!canDismiss()) {
110
+ handleGoBack();
111
+ return false;
306
112
  }
307
- }, [state.isOpen]);
308
-
113
+ return true;
114
+ }, [canDismiss, handleGoBack]);
309
115
 
310
- // Minimal screen props - only navigation-specific
311
- // Screens should use useOxy() hook for OxyContext values (user, sessions, etc.)
312
- // Calculate initialStep first, ensuring it's always a number or undefined
313
- const calculatedInitialStep = typeof state.currentStep === 'number'
314
- ? state.currentStep
315
- : (typeof state.screenProps?.initialStep === 'number'
316
- ? state.screenProps.initialStep
317
- : undefined);
116
+ const handleDismiss = useCallback(() => {
117
+ closeBottomSheet();
118
+ onDismiss?.();
119
+ }, [onDismiss]);
318
120
 
319
- // Extract screenProps without initialStep to avoid conflicts
320
- const { initialStep: _, ...otherScreenProps } = state.screenProps;
121
+ const handleStepChange = useCallback((step: number) => {
122
+ const state = getState();
123
+ updateState({
124
+ currentStep: step,
125
+ screenProps: { ...state.screenProps, initialStep: step },
126
+ });
127
+ }, []);
321
128
 
322
129
  const scrollTo = useCallback((y: number, animated?: boolean) => {
323
- bottomSheetRef.current?.scrollTo(y, animated);
130
+ sheetRef.current?.scrollTo(y, animated);
324
131
  }, []);
325
132
 
326
- const screenProps: BaseScreenProps & { scrollTo: (y: number, animated?: boolean) => void } = {
327
- navigate,
328
- goBack,
329
- onClose: () => managerCloseBottomSheet(),
330
- onAuthenticated: () => managerCloseBottomSheet(),
331
- theme: colorScheme ?? 'light',
332
- currentScreen: state.currentScreen ?? undefined,
333
- initialStep: calculatedInitialStep,
334
- onStepChange: handleStepChange,
335
- scrollTo, // Pass scrollTo method
336
- ...otherScreenProps,
337
- };
338
-
339
- // renderBackground must be called before any conditional returns (React hooks rule)
340
133
  const renderBackground = useCallback(
341
134
  (props: { style?: StyleProp<ViewStyle> }) => (
342
- <View
343
- style={[
344
- styles.background,
345
- { backgroundColor: colors.background },
346
- props.style,
347
- ]}
348
- />
135
+ <View style={[styles.background, { backgroundColor: colors.background }, props.style]} />
349
136
  ),
350
- [colors.background],
137
+ [colors.background]
351
138
  );
352
139
 
353
- // Handle dismissal attempt - check if we can dismiss or need to go back
354
- const handleDismissAttempt = useCallback((): boolean => {
355
- if (!canDismiss()) {
356
- // There's navigation history or steps to go back to - navigate back instead
357
- goBack();
358
- return false; // Prevent dismissal
359
- }
360
- // No history or steps - allow dismissal
361
- return true;
362
- }, [canDismiss, goBack]);
363
-
364
- // Don't render if no screen is set
365
- if (!ScreenComponent || !state.currentScreen) {
366
- return null;
367
- }
140
+ const screenPropsValue = useMemo((): BaseScreenProps & { scrollTo: typeof scrollTo } => {
141
+ const { initialStep: _, ...rest } = screenProps;
142
+ return {
143
+ navigate,
144
+ goBack: handleGoBack,
145
+ onClose: closeBottomSheet,
146
+ onAuthenticated: closeBottomSheet,
147
+ theme: colorScheme ?? 'light',
148
+ currentScreen: currentScreen ?? undefined,
149
+ initialStep: currentStep ?? (screenProps?.initialStep as number | undefined),
150
+ onStepChange: handleStepChange,
151
+ scrollTo,
152
+ ...rest,
153
+ };
154
+ }, [navigate, handleGoBack, colorScheme, currentScreen, currentStep, screenProps, handleStepChange, scrollTo]);
368
155
 
369
156
  return (
370
157
  <BottomSheet
371
- ref={bottomSheetRef}
372
- enablePanDownToClose={true}
158
+ ref={sheetRef}
159
+ enablePanDownToClose
160
+ enableHandlePanningGesture
373
161
  backgroundComponent={renderBackground}
374
- enableHandlePanningGesture={true}
375
162
  style={styles.container}
376
163
  onDismiss={handleDismiss}
377
164
  onDismissAttempt={handleDismissAttempt}
378
165
  >
379
- <AnimatedScreenContainer
380
- fadeAnim={fadeAnim}
381
- scaleAnim={scaleAnim}
382
- screenKey={state.currentScreen}
383
- >
384
- <ScreenComponent {...screenProps} />
385
- </AnimatedScreenContainer>
166
+ {ScreenComponent && currentScreen && <ScreenComponent {...screenPropsValue} />}
386
167
  </BottomSheet>
387
168
  );
388
169
  };
389
170
 
390
- const BottomSheetRouter = React.memo(BottomSheetRouterComponent);
391
- BottomSheetRouter.displayName = 'BottomSheetRouter';
392
-
393
171
  const styles = StyleSheet.create({
394
172
  container: {
395
173
  maxWidth: 800,
@@ -404,4 +182,4 @@ const styles = StyleSheet.create({
404
182
  },
405
183
  });
406
184
 
407
- export default BottomSheetRouter;
185
+ export default React.memo(BottomSheetRouter);
@@ -4,7 +4,6 @@ import { Ionicons } from '@expo/vector-icons';
4
4
  // @ts-ignore - MaterialCommunityIcons is available at runtime
5
5
  import { MaterialCommunityIcons } from '@expo/vector-icons';
6
6
  import { useColorScheme } from '../hooks/use-color-scheme';
7
- import { useHapticPress } from '../hooks/use-haptic-press';
8
7
  import { darkenColor } from '../utils/colorUtils';
9
8
  import { normalizeColorScheme } from '../utils/themeUtils';
10
9
  import { Colors } from '../constants/theme';
@@ -87,13 +86,10 @@ const GroupedItemComponent = ({
87
86
  </View>
88
87
  );
89
88
 
90
- const handlePressIn = useHapticPress();
91
-
92
89
  if (onPress && !disabled) {
93
90
  return (
94
91
  <TouchableOpacity
95
92
  style={itemStyles}
96
- onPressIn={disabled ? undefined : handlePressIn}
97
93
  onPress={onPress}
98
94
  activeOpacity={0.7}
99
95
  accessibilityRole="button"