/ 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  };