react-native#ListRenderItemInfo TypeScript Examples

The following examples show how to use react-native#ListRenderItemInfo. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Basic.tsx    From react-native-sticky-item with MIT License 5 votes vote down vote up
Basic = () => {
  const flatListRef = useRef<FlatList>(null);

  // styles
  const containerStyle = {
    paddingVertical: SEPARATOR_SIZE * 2,
    backgroundColor: 'white',
  };

  // methods
  const handleStickyItemPress = () => Alert.alert('Sticky Item Pressed');
  const handleScrollToEnd = () => {
    const flatlist = flatListRef.current;
    if (flatlist) {
      flatlist.scrollToEnd({ animated: true });
    }
  };
  const handleScrollToStart = () => {
    const flatlist = flatListRef.current;
    if (flatlist) {
      flatlist.scrollToOffset({ animated: true, offset: 0 });
    }
  };
  const handleScrollToIndex = useCallback(index => {
    const flatlist = flatListRef.current;
    if (flatlist) {
      flatlist.scrollToIndex({ index });
    }
  }, []);

  // render
  const renderItem = ({ index }: ListRenderItemInfo<{}>) => (
    <TouchableOpacity onPress={() => Alert.alert(`Item ${index} Pressed`)}>
      <DummyItem
        index={index}
        borderRadius={BORDER_RADIUS}
        width={STORY_WIDTH}
        height={STORY_HEIGHT}
        backgroundColor={'#dfdfdf'}
      />
    </TouchableOpacity>
  );
  return (
    <SafeAreaView style={styles.root}>
      <View style={containerStyle}>
        <StickyItemFlatList
          ref={flatListRef}
          itemWidth={STORY_WIDTH}
          itemHeight={STORY_HEIGHT}
          separatorSize={SEPARATOR_SIZE}
          borderRadius={BORDER_RADIUS}
          stickyItemWidth={STICKY_ITEM_WIDTH}
          stickyItemHeight={STICKY_ITEM_HEIGHT}
          stickyItemBackgroundColors={['#F8F8FA', '#2d88ff']}
          stickyItemContent={BasicSticky}
          onStickyItemPress={handleStickyItemPress}
          data={data}
          renderItem={renderItem}
        />
      </View>
      <View style={styles.buttons}>
        <Button label="< Scroll To Start" onPress={handleScrollToStart} />
        <Button label="Scroll To End >" onPress={handleScrollToEnd} />
      </View>
      <View style={styles.buttons}>
        <Button label="Scroll To 4" onPress={() => handleScrollToIndex(4)} />
        <Button label="Scroll To 9" onPress={() => handleScrollToIndex(9)} />
        <Button label="Scroll To 14" onPress={() => handleScrollToIndex(14)} />
      </View>
    </SafeAreaView>
  );
}
Example #2
Source File: BasicCustomSeparator.tsx    From react-native-sticky-item with MIT License 5 votes vote down vote up
BasicCustomSeparator = () => {
  // methods
  const handleStickyItemPress = () => Alert.alert('Sticky Item Pressed');

  // render
  const renderItem = ({ index }: ListRenderItemInfo<{}>) => (
    <TouchableOpacity onPress={() => Alert.alert(`Item ${index} Pressed`)}>
      <DummyItem
        index={index}
        borderRadius={BORDER_RADIUS}
        width={STORY_WIDTH}
        height={STORY_HEIGHT}
        backgroundColor={'#dfdfdf'}
      />
    </TouchableOpacity>
  );

  const renderSeparator = ({ size }: { size: number }) => {
    return (
      <View style={{ width: size }}>
        <View style={styles.separatorLine} />
      </View>
    );
  };
  return (
    <View style={styles.root}>
      <View style={styles.container}>
        <StickyItemFlatList
          itemWidth={STORY_WIDTH}
          itemHeight={STORY_HEIGHT}
          separatorSize={SEPARATOR_SIZE}
          borderRadius={BORDER_RADIUS}
          stickyItemWidth={STICKY_ITEM_WIDTH}
          stickyItemHeight={STICKY_ITEM_HEIGHT}
          stickyItemBackgroundColors={['#F8F8FA', '#2d88ff']}
          stickyItemContent={BasicSticky}
          onStickyItemPress={handleStickyItemPress}
          data={data}
          renderItem={renderItem}
          ItemSeparatorComponent={renderSeparator}
        />
      </View>
    </View>
  );
}
Example #3
Source File: BasicRTL.tsx    From react-native-sticky-item with MIT License 5 votes vote down vote up
BasicRTL = () => {
  // styles
  const containerStyle = {
    paddingVertical: SEPARATOR_SIZE * 2,
    backgroundColor: '#111',
  };

  // methods
  const handleStickyItemPress = () => Alert.alert('Sticky Item Pressed');

  // render
  const renderItem = ({ index }: ListRenderItemInfo<{}>) => (
    <TouchableOpacity onPress={() => Alert.alert(`Item ${index} Pressed`)}>
      <DummyItem
        index={index}
        borderRadius={BORDER_RADIUS}
        width={STORY_WIDTH}
        height={STORY_HEIGHT}
        backgroundColor={'#dfdfdf'}
      />
    </TouchableOpacity>
  );
  return (
    <View style={styles.root}>
      <View style={containerStyle}>
        <StickyItemFlatList
          itemWidth={STORY_WIDTH}
          itemHeight={STORY_HEIGHT}
          separatorSize={SEPARATOR_SIZE}
          borderRadius={BORDER_RADIUS}
          stickyItemWidth={STICKY_ITEM_WIDTH}
          stickyItemHeight={STICKY_ITEM_HEIGHT}
          isRTL={true}
          stickyItemBackgroundColors={['#222', '#2d88ff']}
          stickyItemContent={BasicSticky}
          onStickyItemPress={handleStickyItemPress}
          data={data}
          renderItem={renderItem}
        />
      </View>
    </View>
  );
}
Example #4
Source File: FacebookStories.tsx    From react-native-sticky-item with MIT License 5 votes vote down vote up
FacebookStories = () => {
  // styles
  const containerStyle = {
    paddingVertical: SEPARATOR_SIZE,
    backgroundColor: 'white',
  };

  // methods
  const handleStickyItemPress = () => Alert.alert('Sticky Item Pressed');

  // render
  const renderItem = ({ index }: ListRenderItemInfo<{}>) => (
    <TouchableOpacity onPress={() => Alert.alert(`Item ${index} Pressed`)}>
      <DummyItem
        index={index}
        borderRadius={BORDER_RADIUS}
        width={STORY_WIDTH}
        height={STORY_HEIGHT}
        backgroundColor={'#dfdfdf'}
      />
    </TouchableOpacity>
  );
  return (
    <View style={styles.root}>
      <View style={containerStyle}>
        <StickyItemFlatList
          itemWidth={STORY_WIDTH}
          itemHeight={STORY_HEIGHT}
          separatorSize={SEPARATOR_SIZE}
          borderRadius={BORDER_RADIUS}
          stickyItemWidth={STICKY_ITEM_WIDTH}
          stickyItemHeight={STICKY_ITEM_HEIGHT}
          stickyItemBackgroundColors={['#F8F8FA', '#fff']}
          stickyItemContent={FacebookStickyStory}
          onStickyItemPress={handleStickyItemPress}
          data={data}
          renderItem={renderItem}
        />
      </View>
    </View>
  );
}
Example #5
Source File: FacebookStoriesRTL.tsx    From react-native-sticky-item with MIT License 5 votes vote down vote up
FacebookStoriesRTL = () => {
  // styles
  const containerStyle = {
    paddingVertical: SEPARATOR_SIZE * 2,
    backgroundColor: '#111',
  };

  // methods
  const handleStickyItemPress = () => Alert.alert('Sticky Item Pressed');

  // render
  const renderItem = ({ index }: ListRenderItemInfo<{}>) => (
    <TouchableOpacity onPress={() => Alert.alert(`Item ${index} Pressed`)}>
      <DummyItem
        index={index}
        borderRadius={BORDER_RADIUS}
        width={STORY_WIDTH}
        height={STORY_HEIGHT}
        backgroundColor={'#333'}
      />
    </TouchableOpacity>
  );
  return (
    <View style={styles.root}>
      <View style={containerStyle}>
        <StickyItemFlatList
          itemWidth={STORY_WIDTH}
          itemHeight={STORY_HEIGHT}
          separatorSize={SEPARATOR_SIZE}
          borderRadius={BORDER_RADIUS}
          stickyItemWidth={STICKY_ITEM_WIDTH}
          stickyItemHeight={STICKY_ITEM_HEIGHT}
          stickyItemBackgroundColors={['#222', '#000']}
          stickyItemContent={props => (
            <FacebookStickyStory {...props} theme="dark" />
          )}
          onStickyItemPress={handleStickyItemPress}
          isRTL={true}
          data={data}
          renderItem={renderItem}
        />
      </View>
    </View>
  );
}
Example #6
Source File: FacebookStoriesStyled.tsx    From react-native-sticky-item with MIT License 5 votes vote down vote up
FacebookStoriesStyled = () => {
  // styles
  const containerStyle = {
    paddingVertical: SEPARATOR_SIZE * 2,
    backgroundColor: '#111',
  };

  // methods
  const handleStickyItemPress = () => Alert.alert('Sticky Item Pressed');

  // render
  const renderItem = ({ index }: ListRenderItemInfo<{}>) => (
    <TouchableOpacity onPress={() => Alert.alert(`Item ${index} Pressed`)}>
      <DummyItem
        index={index}
        borderRadius={BORDER_RADIUS}
        width={STORY_WIDTH}
        height={STORY_HEIGHT}
        backgroundColor={'#333'}
      />
    </TouchableOpacity>
  );
  return (
    <View style={styles.root}>
      <View style={containerStyle}>
        <StickyItemFlatList
          itemWidth={STORY_WIDTH}
          itemHeight={STORY_HEIGHT}
          separatorSize={SEPARATOR_SIZE}
          borderRadius={BORDER_RADIUS}
          stickyItemWidth={STICKY_ITEM_WIDTH}
          stickyItemHeight={STICKY_ITEM_HEIGHT}
          stickyItemBackgroundColors={['#222', '#000']}
          stickyItemContent={props => (
            <FacebookStickyStory {...props} theme="dark" />
          )}
          onStickyItemPress={handleStickyItemPress}
          data={data}
          renderItem={renderItem}
        />
      </View>
    </View>
  );
}
Example #7
Source File: index.tsx    From krmanga with MIT License 5 votes vote down vote up
function BookList({data, loading, refreshing, hasMore, loadData, goBrief}: IProps) {

    const [endReached, setEndReached] = useState<boolean>(false)

    const renderHeader = () => {
        return <View style={styles.header}/>
    }

    const onRefresh = () => {
        if (typeof loadData === "function") {
            loadData(true);
        }
    }

    const onEndReached = () => {
        if (!hasMore || loading) {
            return;
        }
        setEndReached(true)

        if (typeof loadData === "function") {
            loadData(false, () => {
                setEndReached(false)
            });
        }
    }

    const renderFooter = () => {
        if (endReached) {
            return <More/>;
        }
        if (!hasMore) {
            return <End/>;
        }

        return null;
    };

    const renderItem = ({item}: ListRenderItemInfo<IBook>) => {
        return (
            <Item data={item} goBrief={goBrief}/>
        )
    }

    return (
        <FlatList
            data={data}
            extraData={endReached}
            keyExtractor={(item, key) => `item-${key}-key-${key}`}
            ListHeaderComponent={renderHeader}
            onRefresh={onRefresh}
            refreshing={refreshing}
            numColumns={1}
            scrollEventThrottle={1}
            renderItem={renderItem}
            onEndReached={onEndReached}
            onEndReachedThreshold={1}
            ListFooterComponent={renderFooter}
        />
    )
}
Example #8
Source File: index.tsx    From krmanga with MIT License 5 votes vote down vote up
function DarkDrawer({ chapterList, bookInfo, headerHeight, drawerX, goMangaView, hideDrawer }: IProps) {

    const spinValue = useRef(new Animated.Value(0)).current;
    const spin = spinValue.interpolate({
        inputRange: [0, 1],
        outputRange: ["0deg", "360deg"]
    });

    const [isSpin, setIsSpin] = useState<boolean>(true);
    const [data, setData] = useState<IChapter[] | []>([]);

    useEffect(() => {
        setData(chapterList);
    }, [chapterList]);

    const onPress = () => {
        if (typeof hideDrawer === "function") {
            hideDrawer();
        }
    };

    const renderItem = ({ item }: ListRenderItemInfo<IChapter>) => {
        return (
            <Item data={item} goMangaView={goMangaView} />
        );
    };

    const reverse = () => {
        if (isSpin) {
            Animated.timing(
                spinValue,
                {
                    toValue: 0.5,
                    duration: 250,
                    easing: Easing.linear,
                    useNativeDriver: true
                }
            ).start();
        } else {
            Animated.timing(
                spinValue,
                {
                    toValue: 1,
                    duration: 250,
                    easing: Easing.linear,
                    useNativeDriver: true
                }
            ).start(() => spinValue.setValue(0));
        }
        setData([...data.reverse()]);
        setIsSpin(!isSpin);
    };

    return (
        <Animated.View style={[styles.wrapper, {
            transform: [{ translateX: drawerX }]
        }]}>
            <View style={styles.container}>
                <View style={[styles.listContainer, {
                    paddingTop: headerHeight
                }]}>
                    <Header bookInfo={bookInfo} spin={spin} reverse={reverse} />
                    <FlatList
                        data={data}
                        renderItem={renderItem}
                        keyExtractor={(item, key) => `item-${key}-item-${item.id}`}
                    />
                </View>
                <Touchable onPress={onPress} style={styles.transparentView} />
            </View>
        </Animated.View>
    );
}
Example #9
Source File: index.tsx    From krmanga with MIT License 5 votes vote down vote up
function LightDrawer({ chapterList, bookInfo, headerHeight, drawerX, goMangaView, hideDrawer }: IProps) {

    const spinValue = useRef(new Animated.Value(0)).current;
    const spin = spinValue.interpolate({
        inputRange: [0, 1],
        outputRange: ["0deg", "360deg"]
    });

    const [isSpin, setIsSpin] = useState<boolean>(true);
    const [data, setData] = useState<IChapter[] | []>([]);

    useEffect(() => {
        setData(chapterList);
    }, [chapterList]);

    const onPress = () => {
        if (typeof hideDrawer === "function") {
            hideDrawer();
        }
    };

    const renderItem = ({ item }: ListRenderItemInfo<IChapter>) => {
        return (
            <Item data={item} goMangaView={goMangaView} />
        );
    };

    const reverse = () => {
        if (isSpin) {
            Animated.timing(
                spinValue,
                {
                    toValue: 0.5,
                    duration: 250,
                    easing: Easing.linear,
                    useNativeDriver: true
                }
            ).start();
        } else {
            Animated.timing(
                spinValue,
                {
                    toValue: 1,
                    duration: 250,
                    easing: Easing.linear,
                    useNativeDriver: true
                }
            ).start(() => spinValue.setValue(0));
        }

        setData([...data.reverse()]);
        setIsSpin(!isSpin);
    };

    return (
        <Animated.View style={[styles.wrapper, {
            transform: [{ translateX: drawerX }]
        }]}>
            <View style={styles.container}>
                <Touchable onPress={onPress} style={styles.transparentView} />
                <View style={[styles.listContainer, {
                    paddingTop: headerHeight
                }]}>
                    <Header bookInfo={bookInfo} spin={spin} reverse={reverse} />
                    <FlatList
                        data={data}
                        renderItem={renderItem}
                        keyExtractor={(item, key) => `item-${key}-item-${item.id}`}
                    />
                </View>
            </View>
        </Animated.View>
    );
}
Example #10
Source File: Picker.tsx    From react-native-design-kit with MIT License 4 votes vote down vote up
export default function Picker<ItemT>({
  containerStyle,
  titleStyle,
  titleContainerStyle,
  selected,
  selectedContainerStyle,
  selectedTitleStyle,
  listContainerStyle,
  listItemContainerStyle,
  listItemSelectedContainerStyle,
  fullWidth = false,
  animationDuration = 250,
  placeholder = 'Select Option',
  placeholderStyle,
  icon,
  iconContainerStyle,
  iconStartRotation = '-90deg',
  iconEndRotation = '0deg',
  data,
  keyExtractor,
  titleExtractor,
  onSelect,
  renderItem,
  ...props
}: PickerProps<ItemT>) {
  const [selection, setSelection] = useState<
    PickerSelectionInfo<ItemT> | undefined
  >(getInfoFromKey(selected));
  const [toggle, setToggle] = useState(false);
  const layout = useRef<Layout>();
  const refButton = useRef<View>();
  const animation = useRef(new Animated.Value(0)).current;

  function getInfoFromKey(
    key?: string,
  ): PickerSelectionInfo<ItemT> | undefined {
    if (key !== undefined) {
      for (let index = 0; index < data.length; index++) {
        const item = data[index];

        if (keyExtractor(item, index) === key) {
          return {key, index, item};
        }
      }
    }

    return undefined;
  }

  const handleRefButton = useCallback((instance: View | null) => {
    if (instance) {
      refButton.current = instance;
    }
  }, []);

  const handleRunAnimation = useCallback(
    () =>
      Animated.timing(animation, {
        toValue: toggle ? 1 : 0,
        duration: animationDuration,
        useNativeDriver: true,
      }).start(),
    [animation, toggle, animationDuration],
  );

  const handlePressMenuItem = useCallback(
    (key: string, item: ItemT, index: number) => {
      setSelection({key, item, index});
      setToggle(false);
      onSelect({key, item, index});
    },
    [],
  );

  const handlePressButton = useCallback(
    () =>
      refButton.current?.measure((x, y, width, height, pageX, pageY) => {
        layout.current = {
          x,
          y,
          width,
          height,
          pageX,
          pageY,
        };
        setToggle(!toggle);
      }),
    [refButton.current, toggle],
  );

  const handleRenderMenuItem = useCallback(
    (info: ListRenderItemInfo<ItemT>) => {
      const {item, index} = info;
      const key = keyExtractor(item, index);

      return (
        <Touchable
          style={StyleSheet.flatten([
            styles.listItemContainer,
            listItemContainerStyle,
            selection?.index === index &&
              StyleSheet.flatten([
                styles.listItemSelectedContainer,
                listItemSelectedContainerStyle,
              ]),
          ])}
          onPress={() => handlePressMenuItem(key, item, index)}>
          {renderItem(info)}
        </Touchable>
      );
    },
    [
      selection,
      listItemContainerStyle,
      listItemSelectedContainerStyle,
      keyExtractor,
      renderItem,
      handlePressMenuItem,
    ],
  );

  const handleRenderIcon = useMemo(
    () => (
      <Animated.View
        style={StyleSheet.flatten([
          styles.iconContainer,
          iconContainerStyle,
          {
            transform: [
              {
                rotateZ: animation.interpolate({
                  inputRange: [0, 1],
                  outputRange: [iconStartRotation, iconEndRotation],
                }),
              },
            ],
          },
        ])}>
        {icon || <Icon name="chevron-down" />}
      </Animated.View>
    ),
    [animation, icon, iconContainerStyle, iconStartRotation, iconEndRotation],
  );

  const handleRenderTitle = useMemo(() => {
    const title = selection
      ? titleExtractor
        ? titleExtractor(selection.item, selection.index)
        : keyExtractor(selection.item, selection.index)
      : placeholder;

    return (
      <View
        style={StyleSheet.flatten([
          styles.titleContainer,
          titleContainerStyle,
          styles.fixedTitleContainer,
        ])}>
        <Text
          style={StyleSheet.flatten([
            styles.title,
            titleStyle,
            selection
              ? selectedTitleStyle
              : StyleSheet.flatten<TextStyle>([
                  styles.placeholder,
                  placeholderStyle,
                ]),
          ])}>
          {title}
        </Text>
      </View>
    );
  }, [
    selection,
    titleStyle,
    placeholder,
    placeholderStyle,
    selectedTitleStyle,
    titleExtractor,
    keyExtractor,
  ]);

  const handleRenderButton = useMemo(
    () => (
      <Touchable
        testID="button"
        touchableType="normal"
        refView={handleRefButton}
        style={StyleSheet.flatten([
          styles.container,
          containerStyle,
          toggle
            ? StyleSheet.flatten([
                styles.selectedContainer,
                selectedContainerStyle,
              ])
            : {},
          styles.fixedContainer,
        ])}
        onPress={handlePressButton}>
        {handleRenderIcon}
        <View style={fullWidth && styles.sectionTitle}>
          {handleRenderTitle}
        </View>
      </Touchable>
    ),
    [
      toggle,
      animation,
      handleRenderTitle,
      fullWidth,
      containerStyle,
      selectedContainerStyle,
      iconContainerStyle,
      iconStartRotation,
      iconEndRotation,
      icon,
      handleRenderIcon,
      handleRefButton,
    ],
  );

  const handleRenderMenu = useMemo(
    () =>
      layout.current && (
        <Modal
          transparent
          visible={toggle}
          onPressBackdrop={() => setToggle(!toggle)}>
          <Animated.View
            style={StyleSheet.flatten([
              styles.listContainer,
              listContainerStyle,
              styles.fixedListContainer,
              {
                width: layout.current.width,
                left: layout.current.pageX,
                top: layout.current.pageY + layout.current.height,
                opacity: animation.interpolate({
                  inputRange: [0, 1],
                  outputRange: [0, 1],
                }),
              },
            ])}>
            <FlatList
              {...props}
              data={data}
              keyExtractor={keyExtractor}
              renderItem={handleRenderMenuItem}
            />
          </Animated.View>
        </Modal>
      ),
    [
      layout.current,
      toggle,
      props,
      data,
      listContainerStyle,
      keyExtractor,
      handleRenderMenuItem,
    ],
  );

  useDidUpdate(() => setSelection(getInfoFromKey(selected)), [selected]);

  useDidUpdate(handleRunAnimation, [handleRunAnimation]);

  return (
    <>
      {handleRenderButton}
      {handleRenderMenu}
    </>
  );
}
Example #11
Source File: Queue.tsx    From jellyfin-audio-player with MIT License 4 votes vote down vote up
export default function Queue({ header }: Props) {
    const defaultStyles = useDefaultStyles();
    const queue = useQueue();
    const [isRepeating, setIsRepeating] = useState(false);
    const { index: currentIndex } = useCurrentTrack();

    const playTrack = useCallback(async (index: number) => {
        await TrackPlayer.skip(index);
        await TrackPlayer.play();
    }, []);

    const clearQueue = useCallback(async () => {
        await TrackPlayer.reset();
    }, []);

    const toggleLoop = useCallback(() => {
        setIsRepeating((prev) => {
            TrackPlayer.setRepeatMode(prev ? RepeatMode.Off : RepeatMode.Queue);
            return !prev;
        });
    }, []);

    // Retrieve the repeat mode and assign it to the state on component mount
    useEffect(() => {
        TrackPlayer.getRepeatMode()
            .then((mode) => {
                setIsRepeating(mode === RepeatMode.Queue);
            });
    }, []);

    return (
        <Container
            data={queue}
            ListHeaderComponent={
                <>
                    {header}
                    <Header>
                        <Text>{t('queue')}</Text>
                        <Divider style={{ marginHorizontal: 18 }} />
                        <IconButton
                            style={isRepeating ? defaultStyles.activeBackground : undefined}
                            onPress={toggleLoop}
                        >
                            <RepeatIcon
                                fill={isRepeating ? THEME_COLOR : defaultStyles.textHalfOpacity.color}
                                width={ICON_SIZE}
                                height={ICON_SIZE}
                            />
                        </IconButton>
                    </Header>
                </>
            }
            renderItem={({ item: track, index}: ListRenderItemInfo<Track>) => (
                <TouchableHandler id={index} onPress={playTrack} key={index}>
                    <QueueItem 
                        active={currentIndex === index}
                        key={index}
                        alreadyPlayed={currentIndex ? index < currentIndex : false}
                        style={[
                            defaultStyles.border,
                            currentIndex === index ? defaultStyles.activeBackground : {},
                        ]}
                    >
                        <View style={{ flex: 1, marginRight: 16 }}>
                            <Text
                                style={[currentIndex === index ? { color: THEME_COLOR, fontWeight: '500' } : styles.trackTitle, { marginBottom: 2 }]}
                                numberOfLines={1}
                            >
                                {track.title}
                            </Text>
                            <TextHalfOpacity
                                style={currentIndex === index ? { color: THEME_COLOR, fontWeight: '400' } : undefined}
                                numberOfLines={1}
                            >
                                {track.artist}{track.album && ' — ' + track.album}
                            </TextHalfOpacity>
                        </View>
                        <View style={{ marginLeft: 'auto', marginRight: 8 }}>
                            <TextHalfOpacity
                                style={currentIndex === index ? { color: THEME_COLOR, fontWeight: '400' } : undefined}
                            >
                                {ticksToDuration(track.duration || 0)}
                            </TextHalfOpacity>
                        </View>
                        <View>
                            <DownloadIcon trackId={track.backendId} fill={currentIndex === index ? THEME_COLOR + '80' : undefined} />
                        </View>
                    </QueueItem>
                </TouchableHandler>

            )}
            ListFooterComponent={(
                <ClearQueue>
                    <Button title={t('clear-queue')} onPress={clearQueue} />
                </ClearQueue>
            )}
        />
    );
}
Example #12
Source File: index.tsx    From krmanga with MIT License 4 votes vote down vote up
function Category({ dispatch, navigation, category_id, activeStatus, activeModel, bookList, loading, hasMore, refreshing }: IProps) {

    let scrollViewStartOffsetY: number = 0;
    const [endReached, setEndReached] = useState<boolean>(false);

    useFocusEffect(
        React.useCallback(() => {
            dispatch({
                type: "category/setState",
                payload: {
                    activeCategory: category_id
                }
            });
            loadData(true);
        }, [activeStatus])
    );

    const loadData = (refreshing: boolean, callback?: () => void) => {
        dispatch({
            type: `${activeModel}/fetchBookList`,
            payload: {
                refreshing,
                category_id
            },
            callback
        });
    };

    const goBrief = useCallback((data: IBook) => {
        navigation.navigate("Brief", {
            id: data.id
        });
    }, []);

    const renderItem = ({ item }: ListRenderItemInfo<IBook>) => {
        return (
            <BookCover
                key={item.id}
                data={item}
                goBrief={goBrief}
            />
        );
    };

    const onRefresh = () => {
        dispatch({
            type: `${activeModel}/fetchBookList`,
            payload: {
                refreshing: true,
                onRefresh: true,
                category_id
            }
        });
    };

    const renderFooter = () => {
        if (endReached) {
            return <More />;
        }
        if (!hasMore) {
            return <End />;
        }

        return null;
    };

    const onScrollBeginDrag = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
        scrollViewStartOffsetY = nativeEvent.contentOffset.y;
    };

    const onScrollEndDrag = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
        if (scrollViewStartOffsetY > nativeEvent.contentOffset.y) {
            dispatch({
                type: "category/setState",
                payload: {
                    hideHeader: false
                }
            });
        } else {
            dispatch({
                type: "category/setState",
                payload: {
                    hideHeader: true
                }
            });
        }
    };

    const onEndReached = () => {
        if (!hasMore || loading) {
            return;
        }
        setEndReached(true);

        loadData(false, () => {
            setEndReached(false);
        });
    };

    return (
        (loading && refreshing) ? <BookPlaceholder /> :
            <FlatList
                keyExtractor={(item, key) => `item-${item.id}-key-${key}`}
                data={bookList}
                extraData={endReached}
                renderItem={renderItem}
                refreshing={refreshing}
                style={styles.container}
                onRefresh={onRefresh}
                ListFooterComponent={renderFooter}
                scrollEventThrottle={1}
                onScrollBeginDrag={onScrollBeginDrag}
                onScrollEndDrag={onScrollEndDrag}
                numColumns={3}
                onEndReached={onEndReached}
                onEndReachedThreshold={0.1}
            />
    );
}
Example #13
Source File: index.tsx    From krmanga with MIT License 4 votes vote down vote up
function MangaView({
                       navigation, dispatch, isLogin, chapterList, bookInfo,
                       book_id, headerHeight, markRoast, chapter_num, episodeList, hasMore, loading,
                       currentChapterNum, pages
                   }: IProps) {

    const [endReached, setEndReached] = useState<boolean>(false);
    let [time, setTime] = useState<NodeJS.Timeout | null>(null);

    let flatListRef: FlatList<IEpisode> | null = null;
    const topPanelValue = useRef(new Animated.Value(0)).current;
    const bottomPanelValue = useRef(new Animated.Value(0)).current;
    const drawerX = useRef(new Animated.Value(-viewportWidth)).current;
    let panelEnable: boolean = true;


    useEffect(() => {
        loadData(true);
        return () => {
            StatusBar.setHidden(false);
            if (isLogin) {
                dispatch({
                    type: "mangaView/addHistory",
                    payload: {
                        book_id
                    }
                });
                dispatch({
                    type: "history/setScreenReload"
                });
                dispatch({
                    type: "downloadManage/setScreenReload"
                });
            }
            dispatch({
                type: "mangaView/setState",
                payload: {
                    ...initialState
                }
            });
        };
    }, []);

    const loadData = (refreshing: boolean, callback?: () => void) => {
        dispatch({
            type: "mangaView/fetchEpisodeList",
            payload: {
                refreshing,
                markRoast,
                chapter_num,
                book_id
            },
            callback
        });
    };

    const onEndReached = () => {
        if (!hasMore || loading) {
            return;
        }

        setEndReached(true);

        dispatch({
            type: "mangaView/fetchEpisodeList",
            payload: {
                book_id,
                chapter_num: currentChapterNum + 1
            },
            callback: () => {
                setEndReached(false);
            }
        });
    };

    const renderItem = ({ item }: ListRenderItemInfo<IEpisode>) => {
        return (
            <Item panelHandle={panelHandle} data={item} />
        );
    };

    const renderFooter = () => {
        if (endReached) {
            return <More />;
        }
        if (!hasMore) {
            return <End />;
        }

        return null;
    };

    const getItemLayout = (data: any, index: number) => {
        if (data[index] === undefined) {
            return { length: 0, offset: 0, index };
        }

        let offset = 0;
        const length = viewportWidth * data[index].multiple;

        for (let i = 0; i < index; i++) {
            offset += viewportWidth * data[i].multiple;
        }

        return { length: length, offset, index };
    };

    const scrollToIndex = (index: number) => {
        dispatch({
            type: "brief/setState",
            payload: {
                markChapterNum: episodeList[index].chapter_num,
                markRoast: episodeList[index].roast
            }
        });
        flatListRef?.scrollToIndex({ viewPosition: 0, index: index });
    };

    const lastChapter = () => {
        if (!loading) {
            dispatch({
                type: "mangaView/fetchEpisodeList",
                payload: {
                    refreshing: true,
                    book_id,
                    chapter_num: currentChapterNum - 1
                }
            });
        }
    };

    const nextChapter = () => {
        if (!loading) {
            dispatch({
                type: "mangaView/fetchEpisodeList",
                payload: {
                    refreshing: true,
                    book_id,
                    chapter_num: currentChapterNum + 1
                }
            });
        }
    };

    const showDrawer = () => {
        Animated.timing(drawerX, {
            toValue: 0,
            duration: 200,
            useNativeDriver: true
        }).start();
    };

    const hideDrawer = () => {
        Animated.timing(drawerX, {
            toValue: -viewportWidth,
            duration: 200,
            useNativeDriver: true
        }).start();
    };

    const hidePanel = () => {
        if (panelEnable) {
            Animated.parallel([
                Animated.timing(
                    topPanelValue,
                    {
                        toValue: -headerHeight - getStatusBarHeight(),
                        duration: 200,
                        easing: Easing.linear,
                        useNativeDriver: true
                    }
                ),
                Animated.timing(
                    bottomPanelValue,
                    {
                        toValue: hp(25),
                        duration: 200,
                        easing: Easing.linear,
                        useNativeDriver: true
                    }
                )
            ]).start(() => {
                StatusBar.setHidden(true);
                panelEnable = !panelEnable;
            });
        }
    };

    const showPanel = () => {
        if (!panelEnable) {
            Animated.parallel([
                Animated.timing(
                    topPanelValue,
                    {
                        toValue: 0,
                        duration: 200,
                        easing: Easing.linear,
                        useNativeDriver: true
                    }
                ),
                Animated.timing(
                    bottomPanelValue,
                    {
                        toValue: 0,
                        duration: 200,
                        easing: Easing.linear,
                        useNativeDriver: true
                    }
                )
            ]).start(() => {
                StatusBar.setHidden(false);
                panelEnable = !panelEnable;
            });
        }
    };

    const panelHandle = useCallback(() => {
        if (panelEnable) {
            hidePanel();
        } else {
            showPanel();
        }
    }, [panelEnable]);

    const debounce = (cb: any, wait = 250) => {
        if (time !== null) {
            clearTimeout(time);
        }

        let tempTime = setTimeout(() => {
            time = null;
            cb && cb();
        }, wait);

        setTime(tempTime);
    };

    const onScrollEndDrag = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
        let offset_total = 0;
        let current_episode_total = episodeList[0].episode_total;
        let current_chapter_id = episodeList[0].chapter_id;
        let current_chapter_num = episodeList[0].chapter_num;
        let current_number = episodeList[0].number;
        let current_roast = episodeList[0].roast;
        let current_title = episodeList[0].title;
        for (let i = 0; i < episodeList.length; i++) {
            if (nativeEvent.contentOffset.y >= offset_total) {
                current_episode_total = episodeList[i].episode_total;
                current_chapter_id = episodeList[i].chapter_id;
                current_chapter_num = episodeList[i].chapter_num;
                current_number = episodeList[i].number;
                current_roast = episodeList[i].roast;
                current_title = episodeList[i].title;
            } else {
                break;
            }
            offset_total = offset_total + episodeList[i].multiple * viewportWidth;
        }

        debounce(() => {
            dispatch({
                type: "mangaView/setState",
                payload: {
                    currentEpisodeTotal: current_episode_total,
                    currentChapterId: current_chapter_id,
                    currentChapterNum: current_chapter_num,
                    currentNumber: current_number,
                    showCurrentNumber: current_number,
                    currentRoast: current_roast,
                    currentTitle: current_title
                }
            });
            dispatch({
                type: "brief/setState",
                payload: {
                    markChapterNum: current_chapter_num,
                    markRoast: current_roast
                }
            });
        });

        hidePanel();
    };

    const goMangaChapter = useCallback((item: IChapter) => {
        dispatch({
            type: "mangaView/fetchEpisodeList",
            payload: {
                refreshing: true,
                book_id,
                chapter_num: item.chapter_num,
                callback: () => hideDrawer
            }
        });
    }, []);

    return (
        episodeList.length > 0 ?
            <View>
                <StatusBar barStyle="light-content" />
                <TopCtrPanel
                    book_id={book_id}
                    topPanelValue={topPanelValue}
                    navigation={navigation}
                />
                <BottomCtrPanel
                    bottomPanelValue={bottomPanelValue}
                    scrollToIndex={scrollToIndex}
                    showDrawer={showDrawer}
                    lastChapter={lastChapter}
                    nextChapter={nextChapter}
                />
                <FlatList
                    ref={ref => (flatListRef = ref)}
                    data={episodeList}
                    keyExtractor={(item, key) => `item-${item.id}-key-${key}`}
                    renderItem={renderItem}
                    ListFooterComponent={renderFooter}
                    getItemLayout={getItemLayout}
                    onScrollEndDrag={onScrollEndDrag}
                    initialScrollIndex={pages.episode_offset - 1}
                    onEndReached={onEndReached}
                    onEndReachedThreshold={0.1}
                    extraData={endReached}
                />
                <DarkDrawer
                    chapterList={chapterList}
                    bookInfo={bookInfo}
                    headerHeight={headerHeight}
                    drawerX={drawerX}
                    hideDrawer={hideDrawer}
                    goMangaView={goMangaChapter}
                />
                <BottomStatusBar />
            </View> : null
    );
}
Example #14
Source File: index.tsx    From krmanga with MIT License 4 votes vote down vote up
function ChapterManage({
                           navigation, dispatch, book_id, book_image, headerTitle, hasMore, loading,
                           ids, isEdit, refreshing, chapterList
                       }: IProps) {

    const [endReached, setEndReached] = useState<boolean>(false);

    useEffect(() => {
        navigation.setOptions({
            headerTitle
        });
        loadData(true);
    }, []);

    useEffect(() => {
        navigation.setOptions({
            headerRight: () => <HeaderRightBtn isEdit={isEdit} onSubmit={onSubmit} />
        });
    }, [isEdit]);

    const onSubmit = useCallback(() => {
        dispatch({
            type: "chapterManage/setState",
            payload: {
                isEdit: !isEdit,
                ids: []
            }
        });
    }, [isEdit]);

    const loadData = (refreshing: boolean, callback?: () => void) => {
        dispatch({
            type: "chapterManage/fetchChapterList",
            payload: {
                book_id,
                refreshing: refreshing
            },
            callback
        });
    };

    const onRefresh = () => {
        loadData(true);
    };

    const renderItem = ({ item }: ListRenderItemInfo<IChapter>) => {
        const selected = ids.indexOf(item.chapter_num) > -1;
        return (
            <Item
                data={item}
                book_image={book_image}
                isEdit={isEdit}
                selected={selected}
                onClickItem={onClickItem}
            />
        );
    };

    const onClickItem = useCallback((item: IChapter) => {
        if (isEdit) {
            const i = ids.indexOf(item.chapter_num);
            if (i > -1) {
                ids.splice(i, 1);
                dispatch({
                    type: "chapterManage/setState",
                    payload: {
                        ids: [...ids]
                    }
                });
            } else {
                dispatch({
                    type: "chapterManage/setState",
                    payload: {
                        ids: [...ids, item.chapter_num]
                    }
                });
            }
        } else {
            navigation.navigate("Brief", {
                id: book_id
            });
            navigation.navigate("MangaView", {
                book_id,
                chapter_num: item.chapter_num
            });
        }
    }, [isEdit, ids]);

    const destroy = () => {
        dispatch({
            type: "chapterManage/delChapter",
            payload: {
                book_id,
                ids
            }
        });
        dispatch({
            type: "downloadManage/setScreenReload"
        });
    };

    const onEndReached = () => {
        if (!hasMore || loading) {
            return;
        }
        setEndReached(true);
        loadData(false, () => {
            setEndReached(false);
        });
    };

    const renderFooter = () => {
        if (endReached) {
            return <More />;
        }
        if (!hasMore) {
            return <End />;
        }

        return null;
    };


    return (
        <View style={{ flex: 1 }}>
            <FlatList
                keyExtractor={(item, key) => `key-${key}`}
                numColumns={3}
                onRefresh={onRefresh}
                refreshing={refreshing}
                data={chapterList}
                renderItem={renderItem}
                extraData={endReached}
                ListFooterComponent={renderFooter}
                onEndReached={onEndReached}
                onEndReachedThreshold={0.1}
            />
            <EditView
                ids={ids}
                isEdit={isEdit}
                destroy={destroy}
            />
        </View>
    );
}
Example #15
Source File: index.tsx    From krmanga with MIT License 4 votes vote down vote up
function Download({
                      dispatch, navigation, isEdit, isLogin, ids, downloadList,
                      refreshing, hasMore, loading
                  }: IProps) {

    const translateX: Animated.Value = useRef(new Animated.Value(0)).current;
    const [endReached, setEndReached] = useState<boolean>(false);

    useFocusEffect(
        React.useCallback(() => {
            loadData(true);
            return () => {
                dispatch({
                    type: "downloadManage/setState",
                    payload: {
                        isEdit: false,
                        ids: []
                    }
                });
            };
        }, [isLogin])
    );

    const loadData = (refreshing: boolean, callback?: () => void) => {
        if (isLogin) {
            dispatch({
                type: "downloadManage/fetchDownloadList",
                payload: {
                    refreshing: refreshing
                },
                callback
            });
        }
    };

    const onRefresh = () => {
        dispatch({
            type: "downloadManage/setScreenReload"
        });
        loadData(true);
    };

    const onEndReached = () => {
        if (!hasMore || loading) {
            return;
        }
        setEndReached(true);

        loadData(false, () => {
            setEndReached(false);
        });
    };

    const getBeforeX = () => {
        Animated.timing(translateX,
            {
                useNativeDriver: false,
                toValue: wp(5),
                duration: 150
            }
        ).start();
    };

    const getAfterX = () => {
        Animated.timing(translateX,
            {
                useNativeDriver: false,
                toValue: 0,
                duration: 150
            }
        ).start();
    };

    const onClickItem = useCallback((item: IDownList) => {
        if (isEdit) {
            let i = ids.indexOf(item.book_id);
            if (i > -1) {
                ids.splice(i, 1);
                dispatch({
                    type: "downloadManage/setState",
                    payload: {
                        ids: [...ids]
                    }
                });
            } else {
                dispatch({
                    type: "downloadManage/setState",
                    payload: {
                        ids: [...ids, item.book_id]
                    }
                });
            }
        } else {
            navigation.navigate("ChapterManage", {
                book_id: item.book_id,
                book_image: item.image,
                headerTitle: item.title
            });
        }
    }, [isEdit, ids]);

    const goMangaView = useCallback((item: IDownList) => {
        navigation.navigate("Brief", {
            id: item.book_id
        });
        navigation.navigate("MangaView", {
            chapter_num: item.chapter_num ? item.chapter_num : 1,
            markRoast: item.roast,
            book_id: item.book_id
        });
    }, []);

    const renderFooter = () => {
        if (endReached) {
            return <More />;
        }
        if (!hasMore) {
            return <End />;
        }

        return null;
    };

    const renderItem = ({ item }: ListRenderItemInfo<IDownList>) => {
        const selected = ids.indexOf(item.book_id) > -1;
        return (
            <Touchable
                key={item.book_id}
                onPress={() => onClickItem(item)}
            >
                <Animated.View
                    style={{ transform: [{ translateX: translateX }] }}
                >
                    <Item
                        data={item}
                        isEdit={isEdit}
                        selected={selected}
                        goMangaView={goMangaView}
                    />
                </Animated.View>
            </Touchable>
        );
    };

    const cancel = () => {
        const newData = downloadList.map(item => item.book_id);
        if (downloadList.length === ids.length) {
            dispatch({
                type: "downloadManage/setState",
                payload: {
                    ids: []
                }
            });
        } else {
            dispatch({
                type: "downloadManage/setState",
                payload: {
                    ids: newData
                }
            });
        }
    };

    const destroy = () => {
        dispatch({
            type: "downloadManage/delBookCache",
            payload: {
                ids
            }
        });
    };

    if (isEdit) {
        getBeforeX();
    } else {
        getAfterX();
    }

    return (
        !isLogin ? null :
            (loading && refreshing) ? <ListPlaceholder /> :
                <View style={styles.container}>
                    <FlatList
                        keyExtractor={(item, key) => `item-${item.book_id}-key-${key}`}
                        numColumns={1}
                        onRefresh={onRefresh}
                        refreshing={refreshing}
                        data={downloadList}
                        renderItem={renderItem}
                        extraData={endReached}
                        ListFooterComponent={renderFooter}
                        onEndReached={onEndReached}
                        onEndReachedThreshold={0.1}
                    />
                    <EditView
                        data_length={downloadList.length}
                        isEdit={isEdit}
                        ids={ids}
                        cancel={cancel}
                        destroy={destroy}
                    />
                </View>
    );
}
Example #16
Source File: index.tsx    From krmanga with MIT License 4 votes vote down vote up
function Shelf({
                   navigation, dispatch, isLogin, loading,
                   refreshing, hasMore, collectionList, ids, isEdit
               }: IProps) {

    const scrollY: Animated.Value = useRef(new Animated.Value(0)).current;
    const [endReached, setEndReached] = useState<boolean>(false);

    useFocusEffect(
        React.useCallback(() => {
            loadData(true);
            return () => {
                dispatch({
                    type: "collection/setState",
                    payload: {
                        isEdit: false,
                        ids: []
                    }
                });
            };
        }, [isLogin])
    );

    const loadData = (refreshing: boolean, callback?: () => void) => {
        if (isLogin) {
            dispatch({
                type: "collection/fetchCollectionList",
                payload: {
                    refreshing: refreshing
                },
                callback
            });
        }
    };

    const onEndReached = () => {
        if (!hasMore || loading) {
            return;
        }
        setEndReached(true);
        loadData(false, () => {
            setEndReached(false);
        });
    };

    const onClickItem = useCallback((item: ICollection) => {
        if (isEdit) {
            const i = ids.indexOf(item.id);
            if (i > -1) {
                ids.splice(i, 1);
                dispatch({
                    type: "collection/setState",
                    payload: {
                        ids: [...ids]
                    }
                });
            } else {
                dispatch({
                    type: "collection/setState",
                    payload: {
                        ids: [...ids, item.id]
                    }
                });
            }
        } else {
            navigation.navigate("Brief", {
                id: item.book_id
            });
        }
    }, [isEdit, ids]);

    const cancel = () => {
        const newData = collectionList.map(item => item.id);
        if (collectionList.length === ids.length) {
            dispatch({
                type: "collection/setState",
                payload: {
                    ids: []
                }
            });
        } else {
            dispatch({
                type: "collection/setState",
                payload: {
                    ids: newData
                }
            });
        }
    };

    const destroy = () => {
        dispatch({
            type: "collection/delUserCollection",
            payload: {
                ids
            }
        });
    };

    const renderItem = ({ item }: ListRenderItemInfo<ICollection>) => {
        const selected = ids.indexOf(item.id) > -1;
        return (
            <Item
                key={item.id}
                data={item}
                isEdit={isEdit}
                selected={selected}
                onClickItem={onClickItem}
            />
        );
    };

    const renderFooter = () => {
        if (endReached) {
            return <More />;
        }
        if (!hasMore) {
            return <End />;
        }

        return null;
    };

    const getHeaderOpacity = () => {
        return scrollY.interpolate({
            inputRange: [-50, 0],
            outputRange: [1, 0],
            extrapolate: "clamp"
        });
    };

    return (
        !isLogin ? null :
            refreshing ? <BookPlaceholder /> :
                <View style={styles.container}>
                        <Animated.Text style={[styles.total,{
                            opacity: getHeaderOpacity()
                        }]}>总收藏{collectionList.length}本</Animated.Text>
                    <FlatList
                        keyExtractor={(item, key) => `item-${item.id}-key-${key}`}
                        scrollEventThrottle={1}
                        data={collectionList}
                        style={styles.container}
                        numColumns={3}
                        onScroll={Animated.event(
                            [{
                                nativeEvent: { contentOffset: { y: scrollY } }
                            }],
                            {
                                useNativeDriver: false
                            }
                        )}
                        renderItem={renderItem}
                        extraData={endReached}
                        ListFooterComponent={renderFooter}
                        onEndReached={onEndReached}
                        onEndReachedThreshold={0.1}
                    />
                    <EditView
                        data_length={collectionList.length}
                        ids={ids}
                        isEdit={isEdit}
                        cancel={cancel}
                        destroy={destroy}
                    />
                </View>
    );
}