From 09e0447701b405a478b10d45cccb9028d61cd864 Mon Sep 17 00:00:00 2001 From: Violet Caulfield <42452695+anultravioletaurora@users.noreply.github.com> Date: Wed, 5 Nov 2025 19:25:30 -0600 Subject: [PATCH] SwipeableRow enhancements / fixes Fixes issue where the underlying track wasn't tappable Fixes animation when pressing on a track --- .../Global/components/SwipeableRow.tsx | 64 +++++++++++++++---- src/components/Global/components/track.tsx | 10 ++- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/src/components/Global/components/SwipeableRow.tsx b/src/components/Global/components/SwipeableRow.tsx index 56343cba..226bc51c 100644 --- a/src/components/Global/components/SwipeableRow.tsx +++ b/src/components/Global/components/SwipeableRow.tsx @@ -3,13 +3,11 @@ import { XStack, YStack, getToken } from 'tamagui' import { Gesture, GestureDetector } from 'react-native-gesture-handler' import Animated, { Easing, - interpolate, useAnimatedStyle, useSharedValue, withTiming, } from 'react-native-reanimated' import Icon from './icon' -import { Text } from '../helpers/text' import useHapticFeedback from '../../../hooks/use-haptic-feedback' import { notifySwipeableRowClosed, @@ -17,7 +15,7 @@ import { registerSwipeableRow, unregisterSwipeableRow, } from './swipeable-row-registry' -import { runOnJS, runOnUISync, scheduleOnRN, scheduleOnUI } from 'react-native-worklets' +import { scheduleOnRN } from 'react-native-worklets' export type SwipeAction = { label: string @@ -34,6 +32,8 @@ export type QuickAction = { type Props = { children: React.ReactNode + onPress?: () => void | null + onLongPress?: () => void | null leftAction?: SwipeAction | null // immediate action on right swipe leftActions?: QuickAction[] | null // quick action menu on right swipe rightAction?: SwipeAction | null // legacy immediate action on left swipe @@ -47,6 +47,8 @@ type Props = { */ export default function SwipeableRow({ children, + onPress, + onLongPress, leftAction, leftActions, rightAction, @@ -114,14 +116,44 @@ export default function SwipeableRow({ menuOpenRef.current = menuOpen.value }, [menuOpen]) - const schedule = (fn?: () => void) => { - 'worklet' - if (!fn) return - // Defer JS work so the UI bounce plays smoothly - setTimeout(() => fn(), 0) - } + const fgOpacity = useSharedValue(1.0) - const gesture = useMemo(() => { + const tapGesture = useMemo(() => { + return Gesture.Tap() + .runOnJS(true) + .onBegin(() => { + fgOpacity.set(0.5) + }) + .onEnd(() => { + if (onPress) { + triggerHaptic('impactLight') + onPress() + } + }) + .onFinalize(() => { + fgOpacity.set(1.0) + }) + }, [onPress]) + + const longPressGesture = useMemo(() => { + return Gesture.LongPress() + .runOnJS(true) + .onBegin(() => { + fgOpacity.set(0.5) + }) + .onStart(() => { + if (onLongPress) { + triggerHaptic('effectDoubleClick') + onLongPress() + } + fgOpacity.set(1.0) + }) + .onTouchesCancelled(() => { + fgOpacity.set(1.0) + }) + }, [onLongPress]) + + const panGesture = useMemo(() => { return Gesture.Pan() .runOnJS(true) .activeOffsetX([-10, 10]) @@ -129,6 +161,7 @@ export default function SwipeableRow({ .onBegin(() => { if (disabled) return dragging.set(true) + fgOpacity.set(1.0) }) .onUpdate((e) => { if (disabled) return @@ -211,7 +244,14 @@ export default function SwipeableRow({ triggerHaptic, ]) - const fgStyle = useAnimatedStyle(() => ({ transform: [{ translateX: tx.value }] })) + const fgStyle = useAnimatedStyle(() => ({ + transform: [ + { + translateX: tx.value, + }, + ], + opacity: withTiming(fgOpacity.value, { easing: Easing.bounce }), + })) const leftUnderlayStyle = useAnimatedStyle(() => { // Normalize progress to [0,1] with a monotonic denominator to avoid non-monotonic ranges // when the available swipe distance is smaller than the threshold (e.g., 1 quick action = 48px) @@ -234,7 +274,7 @@ export default function SwipeableRow({ if (disabled) return <>{children} return ( - + {/* Left action underlay with colored background (icon-only) */} {leftAction && !leftActions && ( diff --git a/src/components/Global/components/track.tsx b/src/components/Global/components/track.tsx index 8500c531..93655adf 100644 --- a/src/components/Global/components/track.tsx +++ b/src/components/Global/components/track.tsx @@ -26,6 +26,7 @@ import { useApi } from '../../../stores' import { useCurrentTrack, usePlayQueue } from '../../../stores/player/queue' import { useAddFavorite, useRemoveFavorite } from '../../../api/mutations/favorite' import { StackActions } from '@react-navigation/native' +import { TouchableOpacity } from 'react-native' export interface TrackProps { track: BaseItemDto @@ -213,15 +214,18 @@ export default function Track({ return ( - +