@oxyhq/services 5.21.5 → 5.21.7

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 (177) hide show
  1. package/lib/commonjs/crypto/keyManager.js +67 -22
  2. package/lib/commonjs/crypto/keyManager.js.map +1 -1
  3. package/lib/commonjs/index.js +66 -0
  4. package/lib/commonjs/index.js.map +1 -1
  5. package/lib/commonjs/ui/components/BottomSheetRouter.js +100 -286
  6. package/lib/commonjs/ui/components/BottomSheetRouter.js.map +1 -1
  7. package/lib/commonjs/ui/components/GroupedItem.js +0 -3
  8. package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
  9. package/lib/commonjs/ui/components/OxyProvider.js +14 -19
  10. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  11. package/lib/commonjs/ui/components/fileManagement/AnimatedButton.js +57 -0
  12. package/lib/commonjs/ui/components/fileManagement/AnimatedButton.js.map +1 -0
  13. package/lib/commonjs/ui/components/profile/EditBioModal.js +24 -156
  14. package/lib/commonjs/ui/components/profile/EditBioModal.js.map +1 -1
  15. package/lib/commonjs/ui/components/profile/EditDisplayNameModal.js +28 -178
  16. package/lib/commonjs/ui/components/profile/EditDisplayNameModal.js.map +1 -1
  17. package/lib/commonjs/ui/components/profile/EditEmailModal.js +32 -159
  18. package/lib/commonjs/ui/components/profile/EditEmailModal.js.map +1 -1
  19. package/lib/commonjs/ui/components/profile/EditLocationModal.js +45 -227
  20. package/lib/commonjs/ui/components/profile/EditLocationModal.js.map +1 -1
  21. package/lib/commonjs/ui/components/profile/EditUsernameModal.js +30 -155
  22. package/lib/commonjs/ui/components/profile/EditUsernameModal.js.map +1 -1
  23. package/lib/commonjs/ui/hooks/mutations/mutationFactory.js +177 -0
  24. package/lib/commonjs/ui/hooks/mutations/mutationFactory.js.map +1 -0
  25. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +10 -123
  26. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  27. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +2 -32
  28. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
  29. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js +2 -31
  30. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -1
  31. package/lib/commonjs/ui/hooks/useFileFiltering.js +76 -0
  32. package/lib/commonjs/ui/hooks/useFileFiltering.js.map +1 -0
  33. package/lib/commonjs/ui/navigation/bottomSheetManager.js +43 -145
  34. package/lib/commonjs/ui/navigation/bottomSheetManager.js.map +1 -1
  35. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +0 -2
  36. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  37. package/lib/commonjs/ui/screens/FileManagementScreen.js +2 -2
  38. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  39. package/lib/commonjs/ui/utils/authHelpers.js +164 -0
  40. package/lib/commonjs/ui/utils/authHelpers.js.map +1 -0
  41. package/lib/commonjs/ui/utils/avatarUtils.js +18 -61
  42. package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
  43. package/lib/module/crypto/keyManager.js +67 -22
  44. package/lib/module/crypto/keyManager.js.map +1 -1
  45. package/lib/module/index.js +6 -0
  46. package/lib/module/index.js.map +1 -1
  47. package/lib/module/ui/components/BottomSheetRouter.js +102 -284
  48. package/lib/module/ui/components/BottomSheetRouter.js.map +1 -1
  49. package/lib/module/ui/components/GroupedItem.js +0 -3
  50. package/lib/module/ui/components/GroupedItem.js.map +1 -1
  51. package/lib/module/ui/components/OxyProvider.js +14 -19
  52. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  53. package/lib/module/ui/components/fileManagement/AnimatedButton.js +50 -0
  54. package/lib/module/ui/components/fileManagement/AnimatedButton.js.map +1 -0
  55. package/lib/module/ui/components/profile/EditBioModal.js +24 -156
  56. package/lib/module/ui/components/profile/EditBioModal.js.map +1 -1
  57. package/lib/module/ui/components/profile/EditDisplayNameModal.js +28 -178
  58. package/lib/module/ui/components/profile/EditDisplayNameModal.js.map +1 -1
  59. package/lib/module/ui/components/profile/EditEmailModal.js +32 -159
  60. package/lib/module/ui/components/profile/EditEmailModal.js.map +1 -1
  61. package/lib/module/ui/components/profile/EditLocationModal.js +45 -227
  62. package/lib/module/ui/components/profile/EditLocationModal.js.map +1 -1
  63. package/lib/module/ui/components/profile/EditUsernameModal.js +30 -155
  64. package/lib/module/ui/components/profile/EditUsernameModal.js.map +1 -1
  65. package/lib/module/ui/hooks/mutations/mutationFactory.js +173 -0
  66. package/lib/module/ui/hooks/mutations/mutationFactory.js.map +1 -0
  67. package/lib/module/ui/hooks/mutations/useAccountMutations.js +10 -122
  68. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  69. package/lib/module/ui/hooks/queries/useAccountQueries.js +2 -32
  70. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
  71. package/lib/module/ui/hooks/queries/useServicesQueries.js +2 -31
  72. package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -1
  73. package/lib/module/ui/hooks/useFileFiltering.js +72 -0
  74. package/lib/module/ui/hooks/useFileFiltering.js.map +1 -0
  75. package/lib/module/ui/navigation/bottomSheetManager.js +37 -135
  76. package/lib/module/ui/navigation/bottomSheetManager.js.map +1 -1
  77. package/lib/module/ui/screens/AccountSettingsScreen.js +0 -2
  78. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  79. package/lib/module/ui/screens/FileManagementScreen.js +2 -2
  80. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  81. package/lib/module/ui/utils/authHelpers.js +154 -0
  82. package/lib/module/ui/utils/authHelpers.js.map +1 -0
  83. package/lib/module/ui/utils/avatarUtils.js +18 -61
  84. package/lib/module/ui/utils/avatarUtils.js.map +1 -1
  85. package/lib/typescript/commonjs/crypto/keyManager.d.ts.map +1 -1
  86. package/lib/typescript/commonjs/index.d.ts +6 -0
  87. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  88. package/lib/typescript/commonjs/ui/components/BottomSheetRouter.d.ts +2 -7
  89. package/lib/typescript/commonjs/ui/components/BottomSheetRouter.d.ts.map +1 -1
  90. package/lib/typescript/commonjs/ui/components/GroupedItem.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  92. package/lib/typescript/commonjs/ui/components/fileManagement/AnimatedButton.d.ts +16 -0
  93. package/lib/typescript/commonjs/ui/components/fileManagement/AnimatedButton.d.ts.map +1 -0
  94. package/lib/typescript/commonjs/ui/components/profile/EditBioModal.d.ts.map +1 -1
  95. package/lib/typescript/commonjs/ui/components/profile/EditDisplayNameModal.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/ui/components/profile/EditEmailModal.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/ui/components/profile/EditLocationModal.d.ts +1 -0
  98. package/lib/typescript/commonjs/ui/components/profile/EditLocationModal.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/ui/components/profile/EditUsernameModal.d.ts.map +1 -1
  100. package/lib/typescript/commonjs/ui/hooks/mutations/mutationFactory.d.ts +76 -0
  101. package/lib/typescript/commonjs/ui/hooks/mutations/mutationFactory.d.ts.map +1 -0
  102. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +29 -4
  103. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  104. package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts +1 -1
  105. package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts +1 -1
  107. package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/ui/hooks/useFileFiltering.d.ts +29 -0
  109. package/lib/typescript/commonjs/ui/hooks/useFileFiltering.d.ts.map +1 -0
  110. package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts +11 -60
  111. package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts.map +1 -1
  112. package/lib/typescript/commonjs/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/ui/utils/authHelpers.d.ts +99 -0
  114. package/lib/typescript/commonjs/ui/utils/authHelpers.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts.map +1 -1
  116. package/lib/typescript/module/crypto/keyManager.d.ts.map +1 -1
  117. package/lib/typescript/module/index.d.ts +6 -0
  118. package/lib/typescript/module/index.d.ts.map +1 -1
  119. package/lib/typescript/module/ui/components/BottomSheetRouter.d.ts +2 -7
  120. package/lib/typescript/module/ui/components/BottomSheetRouter.d.ts.map +1 -1
  121. package/lib/typescript/module/ui/components/GroupedItem.d.ts.map +1 -1
  122. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  123. package/lib/typescript/module/ui/components/fileManagement/AnimatedButton.d.ts +16 -0
  124. package/lib/typescript/module/ui/components/fileManagement/AnimatedButton.d.ts.map +1 -0
  125. package/lib/typescript/module/ui/components/profile/EditBioModal.d.ts.map +1 -1
  126. package/lib/typescript/module/ui/components/profile/EditDisplayNameModal.d.ts.map +1 -1
  127. package/lib/typescript/module/ui/components/profile/EditEmailModal.d.ts.map +1 -1
  128. package/lib/typescript/module/ui/components/profile/EditLocationModal.d.ts +1 -0
  129. package/lib/typescript/module/ui/components/profile/EditLocationModal.d.ts.map +1 -1
  130. package/lib/typescript/module/ui/components/profile/EditUsernameModal.d.ts.map +1 -1
  131. package/lib/typescript/module/ui/hooks/mutations/mutationFactory.d.ts +76 -0
  132. package/lib/typescript/module/ui/hooks/mutations/mutationFactory.d.ts.map +1 -0
  133. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +29 -4
  134. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  135. package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts +1 -1
  136. package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  137. package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts +1 -1
  138. package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  139. package/lib/typescript/module/ui/hooks/useFileFiltering.d.ts +29 -0
  140. package/lib/typescript/module/ui/hooks/useFileFiltering.d.ts.map +1 -0
  141. package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts +11 -60
  142. package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts.map +1 -1
  143. package/lib/typescript/module/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  144. package/lib/typescript/module/ui/utils/authHelpers.d.ts +99 -0
  145. package/lib/typescript/module/ui/utils/authHelpers.d.ts.map +1 -0
  146. package/lib/typescript/module/ui/utils/avatarUtils.d.ts.map +1 -1
  147. package/package.json +2 -2
  148. package/src/crypto/keyManager.ts +23 -22
  149. package/src/index.ts +25 -0
  150. package/src/ui/components/BottomSheetRouter.tsx +97 -319
  151. package/src/ui/components/GroupedItem.tsx +0 -4
  152. package/src/ui/components/OxyProvider.tsx +13 -18
  153. package/src/ui/components/fileManagement/AnimatedButton.tsx +56 -0
  154. package/src/ui/components/profile/EditBioModal.tsx +38 -176
  155. package/src/ui/components/profile/EditDisplayNameModal.tsx +48 -195
  156. package/src/ui/components/profile/EditEmailModal.tsx +49 -180
  157. package/src/ui/components/profile/EditLocationModal.tsx +76 -263
  158. package/src/ui/components/profile/EditUsernameModal.tsx +47 -175
  159. package/src/ui/hooks/mutations/mutationFactory.ts +215 -0
  160. package/src/ui/hooks/mutations/useAccountMutations.ts +48 -136
  161. package/src/ui/hooks/queries/useAccountQueries.ts +6 -33
  162. package/src/ui/hooks/queries/useServicesQueries.ts +6 -32
  163. package/src/ui/hooks/useFileFiltering.ts +115 -0
  164. package/src/ui/navigation/bottomSheetManager.ts +43 -150
  165. package/src/ui/screens/AccountSettingsScreen.tsx +0 -2
  166. package/src/ui/screens/FileManagementScreen.tsx +2 -2
  167. package/src/ui/utils/authHelpers.ts +183 -0
  168. package/src/ui/utils/avatarUtils.ts +25 -65
  169. package/lib/commonjs/ui/hooks/use-haptic-press.js +0 -21
  170. package/lib/commonjs/ui/hooks/use-haptic-press.js.map +0 -1
  171. package/lib/module/ui/hooks/use-haptic-press.js +0 -17
  172. package/lib/module/ui/hooks/use-haptic-press.js.map +0 -1
  173. package/lib/typescript/commonjs/ui/hooks/use-haptic-press.d.ts +0 -8
  174. package/lib/typescript/commonjs/ui/hooks/use-haptic-press.d.ts.map +0 -1
  175. package/lib/typescript/module/ui/hooks/use-haptic-press.d.ts +0 -8
  176. package/lib/typescript/module/ui/hooks/use-haptic-press.d.ts.map +0 -1
  177. package/src/ui/hooks/use-haptic-press.ts +0 -15
@@ -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"
@@ -16,20 +16,26 @@ setupFonts();
16
16
  // Detect if running on web
17
17
  const isWeb = Platform.OS === 'web';
18
18
 
19
- // Conditionally import native-only components
19
+ // Conditionally import components
20
20
  let KeyboardProvider: any = ({ children }: any) => children;
21
- let BottomSheetRouter: any = () => null;
21
+ let BottomSheetRouter: any = null;
22
22
 
23
+ // KeyboardProvider only on native
23
24
  if (!isWeb) {
24
25
  try {
25
- // Only import on native platforms
26
26
  KeyboardProvider = require('react-native-keyboard-controller').KeyboardProvider;
27
- BottomSheetRouter = require('./BottomSheetRouter').default;
28
27
  } catch {
29
- // Fallback if imports fail
28
+ // KeyboardProvider not available
30
29
  }
31
30
  }
32
31
 
32
+ // BottomSheetRouter works on all platforms
33
+ try {
34
+ BottomSheetRouter = require('./BottomSheetRouter').default;
35
+ } catch {
36
+ // BottomSheetRouter not available
37
+ }
38
+
33
39
  /**
34
40
  * OxyProvider - Universal provider for Expo apps (native + web)
35
41
  *
@@ -199,24 +205,13 @@ const OxyProvider: FC<OxyProviderProps> = ({
199
205
  >
200
206
  {children}
201
207
  {/* Only render bottom sheet router on native */}
202
- {!isWeb && <BottomSheetRouter />}
208
+ {BottomSheetRouter && <BottomSheetRouter />}
203
209
  <Toaster />
204
210
  </OxyContextProvider>
205
211
  </QueryClientProvider>
206
212
  );
207
213
 
208
- // On web, minimal wrappers (GestureHandler and SafeArea work via react-native-web)
209
- if (isWeb) {
210
- return (
211
- <SafeAreaProvider>
212
- <GestureHandlerRootView style={{ flex: 1 }}>
213
- {coreContent}
214
- </GestureHandlerRootView>
215
- </SafeAreaProvider>
216
- );
217
- }
218
-
219
- // On native, full wrappers including KeyboardProvider
214
+ // All platforms use same wrapper (KeyboardProvider is passthrough on web)
220
215
  return (
221
216
  <SafeAreaProvider>
222
217
  <GestureHandlerRootView style={{ flex: 1 }}>
@@ -0,0 +1,56 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { TouchableOpacity, Animated, Easing } from 'react-native';
3
+ // @ts-ignore - MaterialCommunityIcons is available at runtime
4
+ import { MaterialCommunityIcons } from '@expo/vector-icons';
5
+
6
+ interface AnimatedButtonProps {
7
+ isSelected: boolean;
8
+ onPress: () => void;
9
+ icon: string;
10
+ primaryColor: string;
11
+ textColor: string;
12
+ style: any;
13
+ }
14
+
15
+ /**
16
+ * Animated button component for smooth selection transitions
17
+ * Used in file management views for view mode toggles
18
+ */
19
+ export const AnimatedButton: React.FC<AnimatedButtonProps> = ({
20
+ isSelected,
21
+ onPress,
22
+ icon,
23
+ primaryColor,
24
+ textColor,
25
+ style,
26
+ }) => {
27
+ const animatedValue = useRef(new Animated.Value(isSelected ? 1 : 0)).current;
28
+
29
+ useEffect(() => {
30
+ Animated.timing(animatedValue, {
31
+ toValue: isSelected ? 1 : 0,
32
+ duration: 200,
33
+ easing: Easing.out(Easing.ease),
34
+ useNativeDriver: false,
35
+ }).start();
36
+ }, [isSelected, animatedValue]);
37
+
38
+ const backgroundColor = animatedValue.interpolate({
39
+ inputRange: [0, 1],
40
+ outputRange: ['transparent', primaryColor],
41
+ });
42
+
43
+ return (
44
+ <TouchableOpacity onPress={onPress} activeOpacity={0.7}>
45
+ <Animated.View style={[style, { backgroundColor }]}>
46
+ <Animated.View>
47
+ <MaterialCommunityIcons
48
+ name={icon as any}
49
+ size={16}
50
+ color={isSelected ? '#FFFFFF' : textColor}
51
+ />
52
+ </Animated.View>
53
+ </Animated.View>
54
+ </TouchableOpacity>
55
+ );
56
+ };