/ components / SmartBackButton.tsx
SmartBackButton.tsx
  1  import React, { useState, useEffect } from 'react';
  2  import {
  3    View,
  4    TouchableOpacity,
  5    StyleSheet,
  6    Text,
  7    Animated,
  8    useColorScheme,
  9  } from 'react-native';
 10  import { Ionicons } from '@expo/vector-icons';
 11  import { Colors, ColorScheme } from '@/constants/Colors';
 12  import { useNavigationHistory } from '@/hooks/useNavigationHistory';
 13  import { useRouter, usePathname } from 'expo-router';
 14  import { useHapticFeedback } from '@/utils/haptics';
 15  
 16  interface SmartBackButtonProps {
 17    size?: number;
 18    color?: string;
 19    style?: any;
 20    showLabel?: boolean;
 21    customOnPress?: () => void;
 22    disabled?: boolean;
 23  }
 24  
 25  const SmartBackButton: React.FC<SmartBackButtonProps> = ({
 26    size = 24,
 27    color,
 28    style,
 29    showLabel = false,
 30    customOnPress,
 31    disabled = false,
 32  }) => {
 33    const colorScheme = useColorScheme() as ColorScheme;
 34    const { handleBackPress, canGoBack, navigationState } =
 35      useNavigationHistory();
 36    const router = useRouter();
 37    const pathname = usePathname();
 38    const haptics = useHapticFeedback();
 39  
 40    const [isPressed, setIsPressed] = useState(false);
 41    const [backLabel, setBackLabel] = useState('Back');
 42    const scaleAnim = new Animated.Value(1);
 43  
 44    const colors = Colors[colorScheme];
 45    const buttonColor = color || colors.text;
 46    const isDisabled = disabled || !canGoBack;
 47  
 48    useEffect(() => {
 49      // Determine smart back label based on current context
 50      const determineBackLabel = () => {
 51        if (pathname.includes('/manga/') && pathname.includes('/chapter/')) {
 52          return 'Manga';
 53        } else if (pathname.includes('/manga/')) {
 54          // Check if we came from search, home, or bookmarks
 55          const lastNonMangaRoute = navigationState.contextHistory
 56            .slice()
 57            .reverse()
 58            .find(
 59              (entry) =>
 60                !entry.path.includes('/manga/') ||
 61                (entry.path.includes('/manga/') &&
 62                  entry.path.includes('/chapter/'))
 63            );
 64  
 65          if (lastNonMangaRoute) {
 66            if (lastNonMangaRoute.path === '/mangasearch') return 'Search';
 67            if (lastNonMangaRoute.path === '/') return 'Home';
 68            if (lastNonMangaRoute.path === '/bookmarks') return 'Library';
 69            if (lastNonMangaRoute.path === '/settings') return 'Settings';
 70          }
 71          return 'Search'; // Default fallback for manga pages
 72        } else if (pathname === '/settings') {
 73          return 'Home';
 74        } else if (pathname === '/bookmarks') {
 75          return 'Home';
 76        } else if (pathname === '/mangasearch') {
 77          return 'Home';
 78        }
 79        return 'Back';
 80      };
 81  
 82      setBackLabel(determineBackLabel());
 83    }, [pathname, navigationState]);
 84  
 85    const handlePress = async () => {
 86      if (isDisabled) return;
 87  
 88      haptics.onPress();
 89  
 90      // Animate button press
 91      Animated.sequence([
 92        Animated.timing(scaleAnim, {
 93          toValue: 0.9,
 94          duration: 100,
 95          useNativeDriver: true,
 96        }),
 97        Animated.timing(scaleAnim, {
 98          toValue: 1,
 99          duration: 100,
100          useNativeDriver: true,
101        }),
102      ]).start();
103  
104      if (customOnPress) {
105        customOnPress();
106      } else {
107        try {
108          await handleBackPress('tap');
109        } catch (error) {
110          console.error('Smart back navigation failed:', error);
111          // Fallback to home if navigation fails
112          router.replace('/');
113        }
114      }
115    };
116  
117    const getBackgroundColor = () => {
118      if (isDisabled) return 'transparent';
119      if (isPressed) return colors.primary + '20';
120      return 'transparent';
121    };
122  
123    return (
124      <TouchableOpacity
125        style={[
126          styles.container,
127          {
128            backgroundColor: getBackgroundColor(),
129            opacity: isDisabled ? 0.3 : 1,
130          },
131          style,
132        ]}
133        onPress={handlePress}
134        onPressIn={() => setIsPressed(true)}
135        onPressOut={() => setIsPressed(false)}
136        disabled={isDisabled}
137        accessibilityRole="button"
138        accessibilityLabel={`Go back to ${backLabel}`}
139        accessibilityHint={`Navigate back to the previous ${backLabel.toLowerCase()} page`}
140      >
141        <Animated.View
142          style={[styles.content, { transform: [{ scale: scaleAnim }] }]}
143        >
144          <View style={styles.iconContainer}>
145            <Ionicons name="arrow-back" size={size} color={buttonColor} />
146            {navigationState.currentDepth > 1 && (
147              <View
148                style={[
149                  styles.depthIndicator,
150                  { backgroundColor: colors.primary },
151                ]}
152              >
153                <Text style={[styles.depthText, { color: colors.background }]}>
154                  {Math.min(navigationState.currentDepth - 1, 9)}
155                </Text>
156              </View>
157            )}
158          </View>
159  
160          {showLabel && (
161            <Text style={[styles.label, { color: buttonColor }]}>
162              {backLabel}
163            </Text>
164          )}
165        </Animated.View>
166      </TouchableOpacity>
167    );
168  };
169  
170  const styles = StyleSheet.create({
171    container: {
172      padding: 8,
173      borderRadius: 20,
174      justifyContent: 'center',
175      alignItems: 'center',
176      minWidth: 40,
177      minHeight: 40,
178    },
179    content: {
180      flexDirection: 'row',
181      alignItems: 'center',
182    },
183    iconContainer: {
184      position: 'relative',
185      justifyContent: 'center',
186      alignItems: 'center',
187    },
188    depthIndicator: {
189      position: 'absolute',
190      top: -6,
191      right: -6,
192      width: 18,
193      height: 18,
194      borderRadius: 9,
195      justifyContent: 'center',
196      alignItems: 'center',
197    },
198    depthText: {
199      fontSize: 10,
200      fontWeight: 'bold',
201    },
202    label: {
203      marginLeft: 8,
204      fontSize: 16,
205      fontWeight: '500',
206    },
207  });
208  
209  export default SmartBackButton;