/ components / SwipeableMangaCard.tsx
SwipeableMangaCard.tsx
1 import React, { useRef } from 'react'; 2 import { View, Text, Animated } from 'react-native'; 3 import { PanGestureHandler, State } from 'react-native-gesture-handler'; 4 import { Ionicons } from '@expo/vector-icons'; 5 import MangaCard from './MangaCard'; 6 import { useTheme } from '@/constants/ThemeContext'; 7 import { Colors } from '@/constants/Colors'; 8 import { useHapticFeedback } from '@/utils/haptics'; 9 import { MangaCardProps } from '@/types'; 10 import { CacheContext } from '@/services/CacheImages'; 11 12 interface SwipeableMangaCardProps extends MangaCardProps { 13 context?: CacheContext; 14 mangaId?: string; 15 onBookmark?: () => void; 16 onShare?: () => void; 17 showQuickActions?: boolean; 18 } 19 20 export const SwipeableMangaCard: React.FC<SwipeableMangaCardProps> = ({ 21 onBookmark, 22 onShare, 23 showQuickActions = false, 24 ...cardProps 25 }) => { 26 const { actualTheme } = useTheme(); 27 const colors = Colors[actualTheme]; 28 const haptics = useHapticFeedback(); 29 30 const translateX = useRef(new Animated.Value(0)).current; 31 const actionOpacity = useRef(new Animated.Value(0)).current; 32 33 if (!showQuickActions) { 34 return <MangaCard {...cardProps} />; 35 } 36 37 const onGestureEvent = Animated.event( 38 [{ nativeEvent: { translationX: translateX } }], 39 { useNativeDriver: false } 40 ); 41 42 const onHandlerStateChange = (event: any) => { 43 const { state, translationX } = event.nativeEvent; 44 45 if (state === State.ACTIVE) { 46 // Show actions when swiping left 47 if (translationX < -50) { 48 haptics.onSwipe(); 49 Animated.timing(actionOpacity, { 50 toValue: 1, 51 duration: 200, 52 useNativeDriver: true, 53 }).start(); 54 } else { 55 Animated.timing(actionOpacity, { 56 toValue: 0, 57 duration: 200, 58 useNativeDriver: true, 59 }).start(); 60 } 61 } 62 63 if (state === State.END) { 64 if (translationX < -100) { 65 // Trigger action if swiped far enough 66 if (translationX < -150 && onShare) { 67 haptics.onSuccess(); 68 onShare(); 69 } else if (onBookmark) { 70 haptics.onBookmark(); 71 onBookmark(); 72 } 73 } 74 75 // Reset position 76 Animated.parallel([ 77 Animated.spring(translateX, { 78 toValue: 0, 79 useNativeDriver: false, 80 }), 81 Animated.timing(actionOpacity, { 82 toValue: 0, 83 duration: 200, 84 useNativeDriver: true, 85 }), 86 ]).start(); 87 } 88 }; 89 90 return ( 91 <View style={{ position: 'relative' }}> 92 {/* Background Actions */} 93 <Animated.View 94 style={{ 95 position: 'absolute', 96 right: 0, 97 top: 0, 98 bottom: 0, 99 width: 140, 100 flexDirection: 'row', 101 opacity: actionOpacity, 102 }} 103 > 104 <View 105 style={{ 106 flex: 1, 107 backgroundColor: colors.primary, 108 justifyContent: 'center', 109 alignItems: 'center', 110 borderRadius: 8, 111 marginLeft: 4, 112 }} 113 > 114 <Ionicons name="bookmark" size={24} color="#FFFFFF" /> 115 <Text style={{ color: '#FFFFFF', fontSize: 12, marginTop: 4 }}> 116 Bookmark 117 </Text> 118 </View> 119 {onShare && ( 120 <View 121 style={{ 122 flex: 1, 123 backgroundColor: '#34C759', 124 justifyContent: 'center', 125 alignItems: 'center', 126 borderRadius: 8, 127 marginLeft: 4, 128 }} 129 > 130 <Ionicons name="share" size={24} color="#FFFFFF" /> 131 <Text style={{ color: '#FFFFFF', fontSize: 12, marginTop: 4 }}> 132 Share 133 </Text> 134 </View> 135 )} 136 </Animated.View> 137 138 {/* Swipeable Card */} 139 <PanGestureHandler 140 onGestureEvent={onGestureEvent} 141 onHandlerStateChange={onHandlerStateChange} 142 activeOffsetX={[-10, 10]} 143 failOffsetY={[-20, 20]} 144 > 145 <Animated.View 146 style={{ 147 transform: [{ translateX }], 148 }} 149 > 150 <MangaCard {...cardProps} /> 151 </Animated.View> 152 </PanGestureHandler> 153 </View> 154 ); 155 };