/ components / SwipeBackIndicator.tsx
SwipeBackIndicator.tsx
  1  import React from 'react';
  2  import {
  3    Animated,
  4    StyleSheet,
  5    Dimensions,
  6    useColorScheme,
  7    View,
  8  } from 'react-native';
  9  import { Ionicons } from '@expo/vector-icons';
 10  import { BlurView } from 'expo-blur';
 11  
 12  import { Colors, ColorScheme } from '@/constants/Colors';
 13  
 14  const { height } = Dimensions.get('window');
 15  
 16  interface SwipeBackIndicatorProps {
 17    swipeProgress?: Animated.Value;
 18    swipeOpacity?: Animated.Value;
 19    isVisible?: boolean;
 20    customStyles?: any;
 21    size?: 'small' | 'medium' | 'large';
 22    showText?: boolean;
 23  }
 24  
 25  const SwipeBackIndicator: React.FC<SwipeBackIndicatorProps> = ({
 26    swipeProgress,
 27    swipeOpacity,
 28    isVisible = false,
 29    customStyles,
 30    size = 'medium',
 31    showText = false,
 32  }) => {
 33    const colorScheme = useColorScheme() as ColorScheme;
 34    const arrowColor = Colors[colorScheme].primary;
 35    const textColor = Colors[colorScheme].text;
 36  
 37    const sizeConfig = {
 38      small: { iconSize: 20, containerSize: 40 },
 39      medium: { iconSize: 30, containerSize: 60 },
 40      large: { iconSize: 40, containerSize: 80 },
 41    };
 42  
 43    const config = sizeConfig[size];
 44  
 45    const animatedStyle =
 46      swipeProgress && swipeOpacity
 47        ? {
 48            transform: [
 49              {
 50                translateX: swipeProgress.interpolate({
 51                  inputRange: [0, 1],
 52                  outputRange: [-config.containerSize, 20],
 53                }),
 54              },
 55              {
 56                scale: swipeProgress.interpolate({
 57                  inputRange: [0, 0.5, 1],
 58                  outputRange: [0.8, 1.1, 1],
 59                }),
 60              },
 61            ],
 62            opacity: swipeOpacity,
 63          }
 64        : {};
 65  
 66    const staticStyle = isVisible
 67      ? {
 68          opacity: 1,
 69          transform: [{ translateX: 0 }],
 70        }
 71      : {
 72          opacity: 0,
 73          transform: [{ translateX: -config.containerSize }],
 74        };
 75  
 76    const finalStyle = swipeProgress ? animatedStyle : staticStyle;
 77  
 78    return (
 79      <Animated.View
 80        style={[
 81          indicatorStyles.container,
 82          {
 83            width: config.containerSize,
 84            height: config.containerSize,
 85            borderRadius: config.containerSize / 2,
 86          },
 87          finalStyle,
 88          customStyles,
 89        ]}
 90      >
 91        <BlurView
 92          intensity={80}
 93          tint={colorScheme === 'dark' ? 'dark' : 'light'}
 94          style={indicatorStyles.blurContainer}
 95        >
 96          <View style={indicatorStyles.iconContainer}>
 97            <Ionicons
 98              name="arrow-back"
 99              size={config.iconSize}
100              color={arrowColor}
101            />
102          </View>
103          {showText && (
104            <Animated.Text
105              style={[
106                indicatorStyles.text,
107                { color: textColor },
108                swipeProgress
109                  ? {
110                      opacity: swipeProgress.interpolate({
111                        inputRange: [0, 0.5, 1],
112                        outputRange: [0, 1, 0],
113                      }),
114                    }
115                  : {},
116              ]}
117            >
118              Go Back
119            </Animated.Text>
120          )}
121        </BlurView>
122      </Animated.View>
123    );
124  };
125  
126  interface SwipeGestureOverlayProps {
127    enabled?: boolean;
128    children: React.ReactNode;
129    panResponder?: any;
130    swipeProgress?: Animated.Value;
131    swipeOpacity?: Animated.Value;
132    isSwipingBack?: boolean;
133  }
134  
135  export const SwipeGestureOverlay: React.FC<SwipeGestureOverlayProps> = ({
136    enabled = true,
137    children,
138    panResponder,
139    swipeProgress,
140    swipeOpacity,
141    isSwipingBack = false,
142  }) => {
143    if (!enabled || !panResponder) {
144      return <>{children}</>;
145    }
146  
147    return (
148      <View style={indicatorStyles.overlay} {...panResponder.panHandlers}>
149        {children}
150        {isSwipingBack && swipeProgress && swipeOpacity && (
151          <>
152            <SwipeBackIndicator
153              swipeProgress={swipeProgress}
154              swipeOpacity={swipeOpacity}
155              size="medium"
156            />
157            <Animated.View
158              style={[
159                indicatorStyles.screenOverlay,
160                {
161                  opacity: swipeProgress.interpolate({
162                    inputRange: [0, 1],
163                    outputRange: [0, 0.1],
164                  }),
165                },
166              ]}
167            />
168          </>
169        )}
170      </View>
171    );
172  };
173  
174  const indicatorStyles = StyleSheet.create({
175    container: {
176      position: 'absolute',
177      top: height / 2 - 40,
178      left: 20,
179      justifyContent: 'center',
180      alignItems: 'center',
181      elevation: 10,
182      shadowColor: '#000',
183      shadowOffset: {
184        width: 0,
185        height: 2,
186      },
187      shadowOpacity: 0.25,
188      shadowRadius: 3.84,
189      zIndex: 1000,
190    },
191    blurContainer: {
192      width: '100%',
193      height: '100%',
194      borderRadius: 30,
195      justifyContent: 'center',
196      alignItems: 'center',
197      overflow: 'hidden',
198    },
199    iconContainer: {
200      justifyContent: 'center',
201      alignItems: 'center',
202    },
203    text: {
204      fontSize: 12,
205      fontWeight: '600',
206      marginTop: 4,
207      textAlign: 'center',
208    },
209    overlay: {
210      flex: 1,
211    },
212    screenOverlay: {
213      position: 'absolute',
214      top: 0,
215      left: 0,
216      right: 0,
217      bottom: 0,
218      backgroundColor: '#000',
219      pointerEvents: 'none',
220    },
221  });
222  
223  export default SwipeBackIndicator;