Repository navigation
Carousel for web #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Carousel for web #30
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,20 @@ | ||
| import { router } from "expo-router"; | ||
| import React, { useState, useRef } from "react"; | ||
| import React, { useEffect, useState } from "react"; | ||
| import { | ||
| View, | ||
| Text, | ||
| Image, | ||
| StyleSheet, | ||
| TouchableOpacity, | ||
| Dimensions, | ||
| Modal, | ||
| Platform, | ||
| ActivityIndicator, | ||
| } from "react-native"; | ||
| import { Card, Avatar, IconButton, Chip } from "react-native-paper"; | ||
| import Carousel from "react-native-reanimated-carousel"; | ||
| import { ResizeMode, Video } from "expo-av"; | ||
| import { stylesFn } from "@/styles/InfluencerCard.styles"; | ||
| import { useTheme } from "@react-navigation/native"; | ||
| import { | ||
| PinchGestureHandler, | ||
| PinchGestureHandlerGestureEvent, | ||
| TapGestureHandler, | ||
| State, | ||
| } from "react-native-gesture-handler"; | ||
| import Animated, { | ||
| useAnimatedGestureHandler, | ||
|
|
@@ -29,7 +24,7 @@ import Animated, { | |
| } from "react-native-reanimated"; | ||
| import { MaterialCommunityIcons } from "@expo/vector-icons"; | ||
| import Colors from "@/constants/Colors"; | ||
| import { influencers } from "@/constants/Influencers"; | ||
| import CarouselNative from "./ui/carousel/carousel"; | ||
|
|
||
| const { width } = Dimensions.get("window"); | ||
|
|
||
|
|
@@ -57,19 +52,15 @@ interface InfluencerCardPropsType { | |
|
|
||
| const InfluencerCard = (props: InfluencerCardPropsType) => { | ||
| const [bioExpanded, setBioExpanded] = useState(false); | ||
| const [currentIndex, setCurrentIndex] = useState(0); | ||
| const [muted, setMuted] = useState(true); | ||
| const [isPlaying, setIsPlaying] = useState(true); | ||
| const [isZoomed, setIsZoomed] = useState(false); | ||
| const [selectedImage, setSelectedImage] = useState<string | null>(null); | ||
| const [isSwiping, setIsSwiping] = useState(false); | ||
| const [isZoomed, setIsZoomed] = useState(false); | ||
| const [loading, setLoading] = useState(true); | ||
|
|
||
| // Animation values for zoom | ||
| const scale = useSharedValue(1); | ||
| const focalX = useSharedValue(0); | ||
| const focalY = useSharedValue(0); | ||
|
|
||
| const videoRefs = useRef<{ [key: number]: any }>({}); | ||
| const influencer = props.influencer; | ||
| const theme = useTheme(); | ||
| const styles = stylesFn(theme); | ||
|
|
@@ -102,69 +93,24 @@ const InfluencerCard = (props: InfluencerCardPropsType) => { | |
| }; | ||
| }); | ||
|
|
||
| const handleIndexChange = (index: number) => { | ||
| setCurrentIndex(index); | ||
| setIsSwiping(true); | ||
| // Reset swiping state after a short delay | ||
| setTimeout(() => setIsSwiping(false), 150); | ||
| Object.values(videoRefs.current).forEach((videoRef) => { | ||
| if (videoRef && videoRef.seek) { | ||
| videoRef.seek(0); | ||
| setIsPlaying(false); | ||
| } | ||
| }); | ||
| }; | ||
| const onImagePress = (data: any) => { | ||
| setSelectedImage(data); // TODO: data -> uri | ||
| setIsZoomed(true); | ||
| } | ||
|
|
||
| const handleImagePress = (uri: string) => { | ||
| if (!isSwiping) { | ||
| setSelectedImage(uri); | ||
| setIsZoomed(true); | ||
| } | ||
| }; | ||
| useEffect(() => { | ||
| const timer = setTimeout(() => { | ||
| setLoading(false); | ||
| }, 1000); | ||
|
|
||
| const renderMediaItem = ({ item, index }: any) => { | ||
| if (item.type === "image") { | ||
| return ( | ||
| <TapGestureHandler | ||
| onHandlerStateChange={({ nativeEvent }) => { | ||
| if (nativeEvent.state === State.ACTIVE) { | ||
| handleImagePress(item.uri); | ||
| } | ||
| }} | ||
| > | ||
| <Animated.View> | ||
| <Image | ||
| source={{ uri: item.url || item.uri }} | ||
| style={styles.media} | ||
| /> | ||
| </Animated.View> | ||
| </TapGestureHandler> | ||
| ); | ||
| } else { | ||
| return ( | ||
| <Video | ||
| ref={(ref) => { | ||
| if (ref) { | ||
| videoRefs.current[index] = ref; | ||
| } | ||
| }} | ||
| source={ | ||
| item.appleUrl | ||
| ? { | ||
| uri: Platform.OS === "ios" ? item.appleUrl : item.playUrl, | ||
| } | ||
| : require("../assets/videos/ForBiggerJoyrides.mp4") | ||
| } | ||
| style={styles.media} | ||
| resizeMode={ResizeMode.COVER} | ||
| isLooping={false} | ||
| shouldPlay={false} | ||
| useNativeControls | ||
| onError={(error) => console.log("Video Error:", error)} | ||
| /> | ||
| ); | ||
| } | ||
| }; | ||
| return () => { | ||
| clearTimeout(timer); | ||
| }; | ||
| }, []); | ||
|
Comment on lines
+101
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve loading state implementation The current implementation uses a fixed 1-second timeout which might not reflect actual media loading times. This could lead to:
Consider implementing actual loading state tracking: - const [loading, setLoading] = useState(true);
+ const [mediaLoading, setMediaLoading] = useState(true);
- useEffect(() => {
- const timer = setTimeout(() => {
- setLoading(false);
- }, 1000);
-
- return () => {
- clearTimeout(timer);
- };
- }, []);
-
- if (loading) {
+ if (mediaLoading) {
return <ActivityIndicator />;
}Then pass the loading state handler to CarouselNative: <CarouselNative
data={influencer.media}
onImagePress={onImagePress}
+ onLoadComplete={() => setMediaLoading(false)}
/>Also applies to: 111-113 |
||
|
|
||
| if (loading) { | ||
| return <ActivityIndicator />; | ||
| } | ||
|
|
||
| // Rest of the component remains the same... | ||
| return ( | ||
|
|
@@ -199,30 +145,10 @@ const InfluencerCard = (props: InfluencerCardPropsType) => { | |
| /> | ||
| </View> | ||
|
|
||
| <View style={styles.carouselContainer}> | ||
| <Carousel | ||
| loop | ||
| width={Dimensions.get("window").width - 45} | ||
| height={250} | ||
| data={influencer.media} | ||
| renderItem={renderMediaItem} | ||
| onSnapToItem={handleIndexChange} | ||
| style={{ | ||
| padding: 10, | ||
| }} | ||
| /> | ||
| <View style={styles.indicatorContainer}> | ||
| {influencer.media.map((_: any, index: any) => ( | ||
| <View | ||
| key={index} | ||
| style={[ | ||
| styles.indicatorDot, | ||
| currentIndex === index && styles.activeDot, | ||
| ]} | ||
| /> | ||
| ))} | ||
| </View> | ||
| </View> | ||
| <CarouselNative | ||
| data={influencer.media} | ||
| onImagePress={onImagePress} | ||
| /> | ||
|
|
||
| <View style={styles.stats}> | ||
| <View style={styles.statItem}> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import { useRef, useState } from "react"; | ||
| import { | ||
| Dimensions, | ||
| } from "react-native"; | ||
| import Carousel from "react-native-reanimated-carousel"; | ||
|
|
||
| import { View } from "@/components/theme/Themed"; | ||
| import { stylesFn } from "@/styles/InfluencerCard.styles"; | ||
| import { useTheme } from "@react-navigation/native"; | ||
| import RenderMediaItem from "./render-media-item"; | ||
|
|
||
| interface CarouselProps { | ||
| data: any[]; // TODO: Add specific type | ||
| onImagePress?: (data: any) => void; | ||
| } | ||
|
Comment on lines
+12
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Replace The interface MediaItem {
uri: string;
type: 'image' | 'video';
thumbnail?: string;
}
interface CarouselProps {
data: MediaItem[];
onImagePress?: (uri: string) => void;
} |
||
|
|
||
| const CarouselNative: React.FC<CarouselProps> = ({ | ||
| data, | ||
| onImagePress, | ||
| }) => { | ||
| const [currentIndex, setCurrentIndex] = useState(0); | ||
| const [isPlaying, setIsPlaying] = useState(true); | ||
| const [isSwiping, setIsSwiping] = useState(false); | ||
|
|
||
| const videoRefs = useRef<{ [key: number]: any }>({}); | ||
|
|
||
| const theme = useTheme(); | ||
| const styles = stylesFn(theme); | ||
|
|
||
| const handleIndexChange = (index: number) => { | ||
| setCurrentIndex(index); | ||
| setIsSwiping(true); | ||
| // Reset swiping state after a short delay | ||
| setTimeout(() => setIsSwiping(false), 150); | ||
| Object.values(videoRefs.current).forEach((videoRef) => { | ||
| if (videoRef && videoRef.seek) { | ||
| videoRef.seek(0); | ||
| setIsPlaying(false); | ||
| } | ||
| }); | ||
| }; | ||
|
Comment on lines
+35
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve video ref handling with optional chaining and cleanup. The video ref access could be safer and should be cleaned up on unmount: // Inside component:
useEffect(() => {
return () => {
// Cleanup video refs on unmount
Object.values(videoRefs.current).forEach((videoRef) => {
videoRef?.seek?.(0);
});
};
}, []);
// In handleIndexChange:
Object.values(videoRefs.current).forEach((videoRef) => {
videoRef?.seek?.(0);
setIsPlaying(false);
});🧰 Tools🪛 Biome[error] 36-36: Change to an optional chain. Unsafe fix: Change to an optional chain. (lint/complexity/useOptionalChain) |
||
|
|
||
| const handleImagePress = (uri: string) => { | ||
| if (!isSwiping) { | ||
| if (onImagePress) { | ||
| onImagePress(uri); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <View style={styles.carouselContainer}> | ||
| <Carousel | ||
| loop | ||
| width={Dimensions.get("window").width - 45} | ||
| height={250} | ||
| data={data} | ||
| renderItem={({ item, index }) => ( | ||
| <RenderMediaItem | ||
| handleImagePress={handleImagePress} | ||
| index={index} | ||
| item={item} | ||
| videoRefs={videoRefs} | ||
| /> | ||
| )} | ||
| onSnapToItem={handleIndexChange} | ||
| style={{ | ||
| padding: 10, | ||
| }} | ||
| /> | ||
|
|
||
| <View style={styles.indicatorContainer}> | ||
| {data.map((_: any, index: any) => ( | ||
| <View | ||
| key={index} | ||
| style={[ | ||
| styles.indicatorDot, | ||
| currentIndex === index && styles.activeDot, | ||
| ]} | ||
| /> | ||
| ))} | ||
| </View> | ||
| </View> | ||
| ); | ||
| }; | ||
|
|
||
| export default CarouselNative; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve type safety in onImagePress handler.
The
anytype and TODO comment suggest room for improvement:Consider this improvement: