Files
streamyfin/components/seerr/ParallaxSlideShow.tsx
Uruk 4a75e8f551 refactor: rename Jellyseerr to Seerr throughout codebase
Updates branding and naming conventions to use "Seerr" instead of "Jellyseerr" across all files, components, hooks, and translations.

Renames files, functions, classes, variables, and UI text to reflect the new naming convention while maintaining identical functionality. Updates asset references including logo and screenshot images.

Changes API class name, storage keys, atom names, and all related utilities to use "Seerr" prefix. Modifies translation keys and user-facing text to match the rebrand.
2026-01-12 09:26:19 +01:00

172 lines
4.6 KiB
TypeScript

import { FlashList } from "@shopify/flash-list";
import type React from "react";
import {
type PropsWithChildren,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import { Animated, View, type ViewProps } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Text } from "@/components/common/Text";
import { ParallaxScrollView } from "@/components/ParallaxPage";
import { GridSkeleton } from "./GridSkeleton";
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;
isLoading?: boolean;
}
const ParallaxSlideShow = <T,>({
data,
images,
logo,
HeaderContent,
MainContent,
listHeader,
renderItem,
keyExtractor,
onEndReached,
isLoading = false,
}: 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?.()}</View>
</View>
{MainContent?.()}
<View>
{isLoading ? (
<View>
<Text className='text-lg font-bold my-2'>{listHeader}</Text>
<View className='px-4'>
<View className='flex flex-row flex-wrap'>
{Array.from({ length: 9 }, (_, i) => (
<GridSkeleton key={i} index={i} />
))}
</View>
</View>
</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-expect-error
renderItem={({ item, index }) => renderItem(item, index)}
keyExtractor={keyExtractor}
numColumns={3}
ItemSeparatorComponent={() => <View className='h-2 w-2' />}
/>
)}
</View>
</View>
</ParallaxScrollView>
</View>
);
};
export default ParallaxSlideShow;