/ components / SwipeChapterItem.tsx
SwipeChapterItem.tsx
  1  import React, { useRef, useEffect } from 'react';
  2  import {
  3    StyleSheet,
  4    Text,
  5    View,
  6    TouchableOpacity,
  7    Animated,
  8  } from 'react-native';
  9  import { Ionicons } from '@expo/vector-icons';
 10  import Swipeable from 'react-native-gesture-handler/Swipeable';
 11  
 12  interface SwipeableChapterItemProps {
 13    chapter: {
 14      number: string;
 15      title: string;
 16      date: string;
 17    };
 18    isRead: boolean;
 19    isLastItem: boolean;
 20    isCurrentlyLastRead?: boolean;
 21    onPress: () => void;
 22    onLongPress: () => void;
 23    onUnread: () => void;
 24    colors: any;
 25    styles: any;
 26    currentlyOpenSwipeable: Swipeable | null;
 27    setCurrentlyOpenSwipeable: (swipeable: Swipeable | null) => void;
 28  }
 29  
 30  const BUTTON_WIDTH = 75;
 31  
 32  const SwipeableChapterItem: React.FC<SwipeableChapterItemProps> = ({
 33    chapter,
 34    isRead,
 35    isLastItem,
 36    isCurrentlyLastRead = false,
 37    onPress,
 38    onLongPress,
 39    onUnread,
 40    colors,
 41    styles: parentStyles,
 42    currentlyOpenSwipeable,
 43    setCurrentlyOpenSwipeable,
 44  }) => {
 45    const swipeableRef = useRef<Swipeable>(null);
 46  
 47    useEffect(() => {
 48      if (
 49        currentlyOpenSwipeable &&
 50        currentlyOpenSwipeable !== swipeableRef.current
 51      ) {
 52        swipeableRef.current?.close();
 53      }
 54    }, [currentlyOpenSwipeable]);
 55  
 56    const renderRightActions = (
 57      _progress: Animated.AnimatedInterpolation<number>,
 58      dragX: Animated.AnimatedInterpolation<number>
 59    ) => {
 60      const trans = dragX.interpolate({
 61        inputRange: [-BUTTON_WIDTH, 0],
 62        outputRange: [0, BUTTON_WIDTH],
 63        extrapolate: 'clamp',
 64      });
 65  
 66      return (
 67        <Animated.View
 68          style={[
 69            styles.rightActionContainer,
 70            {
 71              transform: [{ translateX: trans }],
 72            },
 73          ]}
 74        >
 75          <TouchableOpacity
 76            style={[styles.button, { backgroundColor: colors.primary }]}
 77            onPress={() => {
 78              onUnread();
 79              swipeableRef.current?.close();
 80            }}
 81          >
 82            <Ionicons name="close-circle-outline" size={24} color="white" />
 83            <Text style={styles.buttonText}>Unread</Text>
 84          </TouchableOpacity>
 85        </Animated.View>
 86      );
 87    };
 88  
 89    // If the chapter is not read, render a non-swipeable view
 90    if (!isRead) {
 91      return (
 92        <View
 93          style={[styles.container, isLastItem && parentStyles.lastChapterItem]}
 94        >
 95          <TouchableOpacity
 96            testID="chapter-item"
 97            onPress={onPress}
 98            onLongPress={onLongPress}
 99            style={[parentStyles.chapterItem, styles.content]}
100          >
101            <View style={parentStyles.chapterInfo}>
102              <Text style={parentStyles.chapterTitle}>{chapter.title}</Text>
103              <Text style={parentStyles.chapterDate}>{chapter.date}</Text>
104            </View>
105            <View style={parentStyles.chapterStatus}>
106              <Ionicons
107                name="ellipse-outline"
108                size={24}
109                color={colors.tabIconDefault}
110              />
111            </View>
112          </TouchableOpacity>
113        </View>
114      );
115    }
116  
117    return (
118      <View
119        style={[styles.container, isLastItem && parentStyles.lastChapterItem]}
120      >
121        <Swipeable
122          testID="chapter-item"
123          ref={swipeableRef}
124          renderRightActions={renderRightActions}
125          friction={2}
126          leftThreshold={30}
127          rightThreshold={40}
128          overshootRight={false}
129          onSwipeableOpen={() => {
130            if (
131              currentlyOpenSwipeable &&
132              currentlyOpenSwipeable !== swipeableRef.current
133            ) {
134              currentlyOpenSwipeable.close();
135            }
136            setCurrentlyOpenSwipeable(swipeableRef.current);
137          }}
138          onSwipeableClose={() => {
139            if (currentlyOpenSwipeable === swipeableRef.current) {
140              setCurrentlyOpenSwipeable(null);
141            }
142          }}
143        >
144          <TouchableOpacity
145            onPress={onPress}
146            onLongPress={onLongPress}
147            style={[
148              parentStyles.chapterItem,
149              styles.content,
150              isCurrentlyLastRead && styles.lastReadChapterItem,
151            ]}
152          >
153            <View style={parentStyles.chapterInfo}>
154              <Text
155                style={[
156                  parentStyles.chapterTitle,
157                  isRead && parentStyles.readChapterTitle,
158                  isCurrentlyLastRead && styles.lastReadChapterText,
159                ]}
160              >
161                {chapter.title}
162              </Text>
163              <Text style={parentStyles.chapterDate}>{chapter.date}</Text>
164            </View>
165            <View style={parentStyles.chapterStatus}>
166              <Ionicons
167                name="checkmark-circle"
168                size={24}
169                color={
170                  isCurrentlyLastRead ? colors.primary : colors.primary + '99'
171                }
172              />
173            </View>
174          </TouchableOpacity>
175        </Swipeable>
176      </View>
177    );
178  };
179  
180  const styles = StyleSheet.create({
181    container: {
182      width: '100%',
183      backgroundColor: 'transparent',
184    },
185    content: {
186      backgroundColor: 'transparent',
187    },
188    rightActionContainer: {
189      width: BUTTON_WIDTH,
190      height: '100%',
191    },
192    button: {
193      width: BUTTON_WIDTH,
194      height: '100%',
195      alignItems: 'center',
196      justifyContent: 'center',
197    },
198    buttonText: {
199      color: 'white',
200      fontSize: 12,
201      marginTop: 4,
202    },
203    lastReadChapterItem: {
204      backgroundColor: 'rgba(0, 128, 0, 0.05)',
205    },
206    lastReadChapterText: {
207      fontWeight: '700',
208    },
209  });
210  
211  export default SwipeableChapterItem;