Files
streamyfin/components/jellyseerr/ParallaxSlideShow.tsx
2025-01-06 20:55:50 -05:00

160 lines
4.2 KiB
TypeScript

import React, {
PropsWithChildren,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import {Dimensions, View, ViewProps} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { ParallaxScrollView } from "@/components/ParallaxPage";
import { Text } from "@/components/common/Text";
import { Animated } from "react-native";
import { FlashList } from "@shopify/flash-list";
import {useFocusEffect} from "expo-router";
const ANIMATION_ENTER = 250;
const ANIMATION_EXIT = 250;
const BACKDROP_DURATION = 5000;
type Render = React.ComponentType<any>
| React.ReactElement
| null
| undefined;
interface Props<T> {
data: T[]
images: string[];
logo?: React.ReactElement;
HeaderContent?: () => React.ReactElement;
MainContent?: () => React.ReactElement;
listHeader: string;
renderItem: (item: T, index: number) => Render;
keyExtractor: (item: T) => string;
onEndReached?: (() => void) | null | undefined;
}
const ParallaxSlideShow = <T extends unknown>({
data,
images,
logo,
HeaderContent,
MainContent,
listHeader,
renderItem,
keyExtractor,
onEndReached,
...props
}: PropsWithChildren<Props<T> & ViewProps>
) => {
const insets = useSafeAreaInsets();
const [currentIndex, setCurrentIndex] = useState(0);
const fadeAnim = useRef(new Animated.Value(0)).current;
const enterAnimation = useCallback(
() =>
Animated.timing(fadeAnim, {
toValue: 1,
duration: ANIMATION_ENTER,
useNativeDriver: true,
}),
[fadeAnim]
);
const exitAnimation = useCallback(
() =>
Animated.timing(fadeAnim, {
toValue: 0,
duration: ANIMATION_EXIT,
useNativeDriver: true,
}),
[fadeAnim]
);
useEffect(() => {
if (images?.length) {
enterAnimation().start();
const intervalId = setInterval(() => {
Animated.sequence([
enterAnimation(),
exitAnimation()
]).start(() => {
fadeAnim.setValue(0);
setCurrentIndex((prevIndex) => (prevIndex + 1) % images?.length);
})
}, BACKDROP_DURATION);
return () => {
clearInterval(intervalId)
};
}
}, [fadeAnim, images, enterAnimation, exitAnimation, setCurrentIndex, currentIndex]);
return (
<View
className="flex-1 relative"
style={{
paddingLeft: insets.left,
paddingRight: insets.right,
}}
>
<ParallaxScrollView
className="flex-1 opacity-100"
headerHeight={300}
onEndReached={onEndReached}
headerImage={
<Animated.Image
key={images?.[currentIndex]}
id={images?.[currentIndex]}
source={{
uri: images?.[currentIndex],
}}
style={{
width: "100%",
height: "100%",
opacity: fadeAnim,
}}
/>
}
logo={logo}
>
<View className="flex flex-col space-y-4 px-4">
<View className="flex flex-row justify-between w-full">
<View className="flex flex-col w-full">
{HeaderContent && HeaderContent()}
</View>
</View>
{MainContent && MainContent()}
<View>
<FlashList
data={data}
ListEmptyComponent={
<View className="flex flex-col items-center justify-center h-full">
<Text className="font-bold text-xl text-neutral-500">
No results
</Text>
</View>
}
contentInsetAdjustmentBehavior="automatic"
ListHeaderComponent={
<Text className="text-lg font-bold my-2">{listHeader}</Text>
}
nestedScrollEnabled
showsVerticalScrollIndicator={false}
//@ts-ignore
renderItem={({ item, index}) => renderItem(item, index)}
keyExtractor={keyExtractor}
numColumns={3}
estimatedItemSize={214}
ItemSeparatorComponent={() => <View className="h-2 w-2" />}
/>
</View>
</View>
</ParallaxScrollView>
</View>
);
}
export default ParallaxSlideShow;