Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 87 additions & 122 deletions src/components/apps/DraggableAppIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
import React, { useRef } from 'react';
import {
StyleSheet,
View,
Pressable,
} from 'react-native';
import React from 'react';
import { StyleSheet, View, Pressable } from 'react-native';
import Animated, {
useAnimatedStyle,
useSharedValue,
withSpring,
runOnJS,
useAnimatedGestureHandler,
withSequence,
withTiming,
} from 'react-native-reanimated';
import {
PanGestureHandler,
LongPressGestureHandler,
State,
} from 'react-native-gesture-handler';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { IOSAppIcon } from './IOSAppIcon';
import { IOSFolderIcon } from './IOSFolderIcon';
import { hapticTrigger } from '@/src/utils/hapticFeedback';
Expand All @@ -36,6 +27,7 @@ interface DraggableAppIconProps {
onDragEnd: (x: number, y: number) => void;
isDragging: boolean;
isDropTarget: boolean;
isOpening?: boolean;
}

export function DraggableAppIcon({
Expand All @@ -52,14 +44,13 @@ export function DraggableAppIcon({
onDragEnd,
isDragging,
isDropTarget,
isOpening,
}: DraggableAppIconProps) {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const scale = useSharedValue(1);
const opacity = useSharedValue(1);
const zIndex = useSharedValue(0);
const longPressRef = useRef<LongPressGestureHandler>(null);
const panRef = useRef<PanGestureHandler>(null);

// Handle drop target animation
React.useEffect(() => {
Expand All @@ -71,65 +62,56 @@ export function DraggableAppIcon({
}
}, [isDropTarget]);

const handleLongPress = () => {
if (!isEditMode) {
onLongPress();
React.useEffect(() => {
if (isOpening) {
scale.value = withTiming(1.2, { duration: 200 });
opacity.value = withTiming(0, { duration: 180 });
} else {
scale.value = withTiming(1, { duration: 150 });
opacity.value = withTiming(1, { duration: 150 });
}
};
}, [isOpening]);

const panHandler = useAnimatedGestureHandler({
onStart: () => {
'worklet';
if (isEditMode) {
scale.value = withSpring(1.1, {
damping: 15,
stiffness: 400,
});
zIndex.value = 1000;
opacity.value = withTiming(0.8, { duration: 150 });
runOnJS(onDragStart)(item, index);
runOnJS(hapticTrigger)('impactMedium');
}
},
onActive: (event) => {
'worklet';
if (isEditMode) {
translateX.value = event.translationX;
translateY.value = event.translationY;
runOnJS(onDragMove)(
position.x + event.translationX,
position.y + event.translationY
);
}
},
onEnd: (event) => {
'worklet';
if (isEditMode) {
const finalX = position.x + event.translationX;
const finalY = position.y + event.translationY;

runOnJS(onDragEnd)(finalX, finalY);

// Animate back to original position
translateX.value = withSpring(0, {
damping: 20,
stiffness: 400,
});
translateY.value = withSpring(0, {
damping: 20,
stiffness: 400,
});
scale.value = withSpring(1, {
damping: 15,
stiffness: 400,
});
opacity.value = withTiming(1, { duration: 150 });
zIndex.value = 0;

runOnJS(hapticTrigger)('impactLight');

const panGesture = Gesture.Pan()
.enabled(isEditMode)
.onBegin(() => {
scale.value = withSpring(1.1, { damping: 15, stiffness: 400 });
zIndex.value = 1000;
opacity.value = withTiming(0.8, { duration: 150 });
runOnJS(onDragStart)(item, index);
runOnJS(hapticTrigger)('impactMedium');
})
.onUpdate((event) => {
translateX.value = event.translationX;
translateY.value = event.translationY;
runOnJS(onDragMove)(
position.x + event.translationX,
position.y + event.translationY
);
})
.onEnd((event) => {
const finalX = position.x + event.translationX;
const finalY = position.y + event.translationY;
runOnJS(onDragEnd)(finalX, finalY);

translateX.value = withSpring(0, { damping: 20, stiffness: 400 });
translateY.value = withSpring(0, { damping: 20, stiffness: 400 });
scale.value = withSpring(1, { damping: 15, stiffness: 400 });
opacity.value = withTiming(1, { duration: 150 });
zIndex.value = 0;
runOnJS(hapticTrigger)('impactLight');
});

const longPressGesture = Gesture.LongPress()
.minDuration(400)
.onStart(() => {
if (!isEditMode) {
runOnJS(onLongPress)();
}
},
});
});

const combinedGesture = Gesture.Simultaneous(panGesture, longPressGesture);

const animatedStyle = useAnimatedStyle(() => {
return {
Expand All @@ -144,58 +126,41 @@ export function DraggableAppIcon({
});

return (
<LongPressGestureHandler
ref={longPressRef}
onHandlerStateChange={(event) => {
if (event.nativeEvent.state === State.ACTIVE) {
handleLongPress();
}
}}
minDurationMs={400}
simultaneousHandlers={panRef}
>
<PanGestureHandler
ref={panRef}
onGestureEvent={panHandler}
simultaneousHandlers={longPressRef}
waitFor={longPressRef}
enabled={isEditMode}
>
<Pressable onPress={onPress} disabled={isEditMode}>
<Animated.View style={[styles.container, animatedStyle]}>
{isDragging ? (
<View style={styles.placeholder} />
) : (
<>
{isFolder ? (
<IOSFolderIcon
id={item.id}
name={item.name}
apps={item.apps}
color={item.color}
isEditMode={isEditMode}
onPress={() => {}}
onLongPress={() => {}}
onDelete={onDelete}
/>
) : (
<IOSAppIcon
id={item.id}
name={item.name}
url={item.url}
iconUrl={item.iconUrl}
isEditMode={isEditMode}
onPress={() => {}}
onLongPress={() => {}}
onDelete={onDelete}
/>
)}
</>
)}
</Animated.View>
</Pressable>
</PanGestureHandler>
</LongPressGestureHandler>
<GestureDetector gesture={combinedGesture}>
<Pressable onPress={onPress} disabled={isEditMode}>
<Animated.View style={[styles.container, animatedStyle]}>
{isDragging ? (
<View style={styles.placeholder} />
) : (
<>
{isFolder ? (
<IOSFolderIcon
id={item.id}
name={item.name}
apps={item.apps}
color={item.color}
isEditMode={isEditMode}
onPress={() => {}}
onLongPress={() => {}}
onDelete={onDelete}
/>
) : (
<IOSAppIcon
id={item.id}
name={item.name}
url={item.url}
iconUrl={item.iconUrl}
isEditMode={isEditMode}
onPress={() => {}}
onLongPress={() => {}}
onDelete={onDelete}
/>
)}
</>
)}
</Animated.View>
</Pressable>
</GestureDetector>
);
}

Expand Down
Loading