import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useKeyboardHeight } from '@/hooks/useKeyboardHeight'; // Make sure this path is correct
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import React, { useEffect } from 'react';
import {
Dimensions,
Modal,
ScrollView,
TouchableWithoutFeedback,
ViewStyle,
} from 'react-native';
import {
Gesture,
GestureDetector,
GestureHandlerRootView,
} from 'react-native-gesture-handler';
import Animated, {
runOnJS,
useAnimatedStyle,
useSharedValue,
withSpring,
withTiming,
} from 'react-native-reanimated';
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
const MAX_TRANSLATE_Y = -SCREEN_HEIGHT + 50;
type BottomSheetContentProps = {
children: React.ReactNode;
title?: string;
style?: ViewStyle;
rBottomSheetStyle: any;
cardColor: string;
mutedColor: string;
onHandlePress?: () => void;
};
// Component for the bottom sheet content
// It now includes a ScrollView by default for better form handling.
const BottomSheetContent = ({
children,
title,
style,
rBottomSheetStyle,
cardColor,
mutedColor,
onHandlePress,
}: BottomSheetContentProps) => {
return (
{/* Handle */}
{/* Title */}
{title && (
{title}
)}
{/* Content now wrapped in a ScrollView */}
{children}
);
};
type BottomSheetProps = {
isVisible: boolean;
onClose: () => void;
children: React.ReactNode;
snapPoints?: number[];
enableBackdropDismiss?: boolean;
title?: string;
style?: ViewStyle;
disablePanGesture?: boolean;
};
export function BottomSheet({
isVisible,
onClose,
children,
snapPoints = [0.3, 0.6, 0.9],
enableBackdropDismiss = true,
title,
style,
disablePanGesture = false,
}: BottomSheetProps) {
const cardColor = useColor('card');
const mutedColor = useColor('muted');
const { keyboardHeight, isKeyboardVisible } = useKeyboardHeight();
const translateY = useSharedValue(0);
const context = useSharedValue({ y: 0 });
const opacity = useSharedValue(0);
const currentSnapIndex = useSharedValue(0);
// Shared value to hold keyboard height for use in worklets
const keyboardHeightSV = useSharedValue(0);
const snapPointsHeights = snapPoints.map((point) => -SCREEN_HEIGHT * point);
const defaultHeight = snapPointsHeights[0];
const [modalVisible, setModalVisible] = React.useState(false);
// Effect to handle opening and closing the bottom sheet
useEffect(() => {
if (isVisible) {
setModalVisible(true);
translateY.value = withSpring(defaultHeight, {
damping: 50,
stiffness: 400,
});
opacity.value = withTiming(1, { duration: 300 });
currentSnapIndex.value = 0;
} else {
translateY.value = withSpring(0, { damping: 50, stiffness: 400 });
opacity.value = withTiming(0, { duration: 300 }, (finished) => {
if (finished) {
runOnJS(setModalVisible)(false);
}
});
}
}, [isVisible, defaultHeight]);
// Function to animate the sheet to a specific destination
const scrollTo = (destination: number) => {
'worklet';
translateY.value = withSpring(destination, { damping: 50, stiffness: 400 });
};
// --- START: NEW KEYBOARD HANDLING LOGIC ---
useEffect(() => {
// Update the shared value whenever keyboardHeight changes
keyboardHeightSV.value = keyboardHeight;
// Only adjust position if the sheet is currently visible
if (isVisible) {
const currentSnapHeight = snapPointsHeights[currentSnapIndex.value];
let destination: number;
if (isKeyboardVisible) {
// Keyboard is open, move sheet up by keyboard height
destination = currentSnapHeight - keyboardHeight;
} else {
// Keyboard is closed, return to original snap point
destination = currentSnapHeight;
}
scrollTo(destination);
}
}, [keyboardHeight, isKeyboardVisible, isVisible]);
// --- END: NEW KEYBOARD HANDLING LOGIC ---
const findClosestSnapPoint = (currentY: number) => {
'worklet';
// Adjust the currentY by the keyboard height to find the original snap point
const adjustedY = currentY + keyboardHeightSV.value;
let closest = snapPointsHeights[0];
let minDistance = Math.abs(adjustedY - closest);
let closestIndex = 0;
for (let i = 0; i < snapPointsHeights.length; i++) {
const snapPoint = snapPointsHeights[i];
const distance = Math.abs(adjustedY - snapPoint);
if (distance < minDistance) {
minDistance = distance;
closest = snapPoint;
closestIndex = i;
}
}
currentSnapIndex.value = closestIndex;
return closest;
};
const handlePress = () => {
const nextIndex = (currentSnapIndex.value + 1) % snapPointsHeights.length;
currentSnapIndex.value = nextIndex;
const destination = snapPointsHeights[nextIndex] - keyboardHeightSV.value;
scrollTo(destination);
};
const animateClose = () => {
'worklet';
translateY.value = withSpring(0, { damping: 50, stiffness: 400 });
opacity.value = withTiming(0, { duration: 300 }, (finished) => {
if (finished) {
runOnJS(onClose)();
}
});
};
const gesture = Gesture.Pan()
.onStart(() => {
context.value = { y: translateY.value };
})
.onUpdate((event) => {
const newY = context.value.y + event.translationY;
if (newY <= 0 && newY >= MAX_TRANSLATE_Y) {
translateY.value = newY;
}
})
.onEnd((event) => {
const currentY = translateY.value;
const velocity = event.velocityY;
if (velocity > 500 && currentY > -SCREEN_HEIGHT * 0.2) {
animateClose();
return;
}
// Find the closest original snap point
const closestSnapPoint = findClosestSnapPoint(currentY);
// Calculate the final destination, accounting for the keyboard height
const finalDestination = closestSnapPoint - keyboardHeightSV.value;
scrollTo(finalDestination);
});
const rBottomSheetStyle = useAnimatedStyle(() => {
return {
transform: [{ translateY: translateY.value }],
};
});
const rBackdropStyle = useAnimatedStyle(() => {
return {
opacity: opacity.value,
};
});
const handleBackdropPress = () => {
if (enableBackdropDismiss) {
animateClose();
}
};
return (
{disablePanGesture ? (
runOnJS(handlePress)()}
/>
) : (
runOnJS(handlePress)()}
/>
)}
);
}
// Hook for managing bottom sheet state
export function useBottomSheet() {
const [isVisible, setIsVisible] = React.useState(false);
const open = React.useCallback(() => {
setIsVisible(true);
}, []);
const close = React.useCallback(() => {
setIsVisible(false);
}, []);
const toggle = React.useCallback(() => {
setIsVisible((prev) => !prev);
}, []);
return {
isVisible,
open,
close,
toggle,
};
}