diff --git a/frontend/src/Components/LikesTab.js b/frontend/src/Components/LikesTab.js index 0728e45a..f3c1ee98 100644 --- a/frontend/src/Components/LikesTab.js +++ b/frontend/src/Components/LikesTab.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { View, Text, StyleSheet, ScrollView } from "react-native"; +import { View, Text, StyleSheet, ScrollView, RefreshControl } from "react-native"; import { useTheme } from "../styles/ThemeContext"; import { getUserLikedPosts, getLikesOfPost, getLikesOfReview } from "../Services/LikesApiService"; import { getCountCommentsOfPost, getCountCommentsOfReview, removePost, removeReview } from "../Services/PostsApiServices"; @@ -8,7 +8,7 @@ import Post from "./Post"; import Review from "./Review"; -export default function LikesTab({ userInfo, userProfile, handleCommentPress, refreshing }) { +export default function LikesTab({ userInfo, userProfile, handleCommentPress, refreshing, onRefresh }) { const [likedPosts, setLikedPosts] = useState([]); const [loading, setLoading] = useState(false); const { theme } = useTheme(); @@ -88,10 +88,11 @@ export default function LikesTab({ userInfo, userProfile, handleCommentPress, re useEffect(() => { fetchLikedPosts(); - }, []); + }, [refreshing]); const handleRefresh = () => { if (refreshing) { + console.log("refreshing"); fetchLikedPosts(); } }; diff --git a/frontend/src/Components/PostsTab.js b/frontend/src/Components/PostsTab.js index 5bda0efa..33f7f27a 100644 --- a/frontend/src/Components/PostsTab.js +++ b/frontend/src/Components/PostsTab.js @@ -102,7 +102,9 @@ export default function PostsTab({ userInfo, userProfile, handleCommentPress, re useEffect(() => { fetchPostsAndReviews(); - }, []); + }, [refreshing]); + + if (refreshing) { console.log("refreshing"); diff --git a/frontend/src/Components/Review.js b/frontend/src/Components/Review.js index c4310f1e..f6ff86d2 100644 --- a/frontend/src/Components/Review.js +++ b/frontend/src/Components/Review.js @@ -220,7 +220,7 @@ export default function Review({ Review - + @@ -232,7 +232,7 @@ export default function Review({ - {rating} + {rating} @@ -244,7 +244,7 @@ export default function Review({ {likeCount} @@ -254,7 +254,7 @@ export default function Review({ onPress={() => { handleCommentPress(reviewId, true); }}> - + {comments} diff --git a/frontend/src/Components/Watchlist.js b/frontend/src/Components/Watchlist.js index 75f71ac5..2a671cb1 100644 --- a/frontend/src/Components/Watchlist.js +++ b/frontend/src/Components/Watchlist.js @@ -1,12 +1,12 @@ import React, { useState, useEffect } from "react"; -import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Modal, Image, Alert } from "react-native"; +import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Modal, Image, Alert, RefreshControl } from "react-native"; import { MaterialIcons } from "@expo/vector-icons"; import { useNavigation } from '@react-navigation/native'; import { getUserWatchlists } from "../Services/UsersApiService"; import { deleteWatchlist } from "../Services/ListApiService"; // Import the deleteWatchlist function import { useTheme } from "../styles/ThemeContext"; -const WatchlistTab = ({ userInfo }) => { +const WatchlistTab = ({ userInfo , refreshing, onRefresh}) => { const [modalVisible, setModalVisible] = useState(false); const [selectedWatchlist, setSelectedWatchlist] = useState(null); @@ -14,7 +14,7 @@ const WatchlistTab = ({ userInfo }) => { const navigation = useNavigation(); const {theme} = useTheme(); // Fetch user watchlists - useEffect(() => { + const fetchUserWatchlists = async () => { try { const userId = userInfo.userId; @@ -32,8 +32,18 @@ const WatchlistTab = ({ userInfo }) => { } }; + + useEffect(() => { + fetchUserWatchlists(); + }, [refreshing]); + + + + if (refreshing) { + console.log("refreshing"); fetchUserWatchlists(); - }, []); + } + const openOptionsMenu = (watchlist) => { setSelectedWatchlist(watchlist); diff --git a/frontend/src/Screens/ExplorePage.js b/frontend/src/Screens/ExplorePage.js index 9433a6d3..b95bac77 100644 --- a/frontend/src/Screens/ExplorePage.js +++ b/frontend/src/Screens/ExplorePage.js @@ -1,5 +1,5 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; -import { StyleSheet, Text, View, ScrollView, FlatList, TextInput } from 'react-native'; +import { StyleSheet, Text, View, ScrollView, FlatList, TextInput , RefreshControl} from 'react-native'; import { useNavigation, useFocusEffect } from "@react-navigation/native"; import { Ionicons } from '@expo/vector-icons'; import { TouchableOpacity } from "react-native"; @@ -22,6 +22,7 @@ import Post from "../Components/Post"; // To render posts import Review from "../Components/Review"; // To render reviews import moment from "moment"; import { useTheme } from '../styles/ThemeContext'; +import { colors, themeStyles } from "../styles/theme"; import SearchBar from '../Components/SearchBar'; @@ -41,10 +42,10 @@ export default function ExplorePage({ route }) { const [recentRooms, setRecentRooms] = useState([]); const [searchResults, setSearchResults] = useState([]); const [sortedContent, setSortedContent] = useState([]); + const [refreshing, setRefreshing] = useState(false); const keywords = ["art", "city", "neon", "space", "movie", "night", "stars", "sky", "sunset", "sunrise"]; - useEffect(() => { const fetchContent = async () => { try { const friendsContent = await getFriendsOfFriendsContent(userInfo); @@ -86,9 +87,10 @@ export default function ExplorePage({ route }) { console.error('Error fetching content:', error); } }; - - fetchContent(); - }, [userInfo]); + + useEffect(() => { + fetchContent(); + }, [userInfo]); useFocusEffect( useCallback(() => { @@ -133,14 +135,19 @@ export default function ExplorePage({ route }) { console.error("Failed to fetch friends content:", error); } }, [userInfo]); + + useEffect(() => { + if (userInfo) { + fetchFriendsContent(); + } + }, [userInfo, fetchFriendsContent]); + + const onRefresh = useCallback(async () => { + setRefreshing(true); + await Promise.all([fetchContent(), fetchFriendsContent()]); + setRefreshing(false); + }, []); - useFocusEffect( - useCallback(() => { - if (userInfo) { - fetchFriendsContent(); - } - }, [userInfo, fetchFriendsContent]) - ); const handleOpenHub = () => { navigation.navigate("HubScreen", { userInfo }); @@ -305,7 +312,10 @@ export default function ExplorePage({ route }) { }); return ( - + + }> {searchResults.length > 0 ? ( diff --git a/frontend/src/Screens/Home.js b/frontend/src/Screens/Home.js index 0e7e827b..8258b496 100644 --- a/frontend/src/Screens/Home.js +++ b/frontend/src/Screens/Home.js @@ -1,8 +1,9 @@ import React, { useState, useEffect, useRef, useCallback } from "react"; -import { StyleSheet, Text, View, StatusBar, Animated, Platform, Image, Dimensions, FlatList, Pressable, LogBox, SafeAreaView,ScrollView , TouchableOpacity,} from "react-native"; +import { StyleSheet, Text, View, StatusBar, Animated, Platform, Image, Dimensions, FlatList, Pressable, LogBox, SafeAreaView,ScrollView , TouchableOpacity, ActivityIndicator} from "react-native"; import { LinearGradient } from "expo-linear-gradient"; import { useTheme } from "../styles/ThemeContext"; import { colors, themeStyles } from "../styles/theme"; +import FastImage from 'react-native-fast-image'; import Svg from "react-native-svg"; import MovieCard from "../Components/MovieCard" import TrendingMovie from "../Components/TrendingMovies" @@ -15,9 +16,13 @@ import {getFollowedUsersWatchlists} from "../Services/ListApiService" import BottomHeader from "../Components/BottomHeader"; import Genres from "../Components/Genres"; import Rating from "../Components/Rating"; +import Post from "../Components/Post"; +import Review from "../Components/Review"; import HomeHeader from "../Components/HomeHeader"; +import CommentsModal from "../Components/CommentsModal"; import moment from "moment"; -import { getPopularMovies, getMoviesByGenre, getMovieDetails, getNewMovies, getTopPicksForToday, fetchClassicMovies } from '../Services/TMDBApiService'; +import NonFollowerPost from "../Components/NonFollowerPost"; +import { getPopularMovies, getMoviesByGenre, getMovieDetails, getNewMovies, getTopPicksForToday, fetchClassicMovies, fetchCurrentlyPlayingMovies } from '../Services/TMDBApiService'; import { getUserProfile, getFollowingCount, getFollowersCount } from "../Services/UsersApiService"; import { getUserWatchlists } from "../Services/UsersApiService"; @@ -128,6 +133,7 @@ const Home = ({ route }) => { const [isPost, setIsPost] = useState(false); const [comments, setComments] = useState([]); const [loadingComments, setLoadingComments] = useState(false); + const [loading, setLoading] = useState(true); const [selectedPostId, setSelectedPostId] = useState(null); // Add this line // const [friendsContent, setFriendsContent] = useState([]); const [sortedContent, setSortedContent] = useState([]); @@ -142,55 +148,36 @@ const Home = ({ route }) => { const [watchlists, setWatchlists] = useState([]); + useEffect(() => { - const fetchMoviesByGenres = async () => { - try { - const genreMoviesPromises = Object.entries(genres).map(async ([genreName, genreId]) => { - const movies = await getNewMovies(genreId); // Using the imported function here - return { genre: genreName, movies }; - }); - - const fetchedMovies = await Promise.all(genreMoviesPromises); - - const moviesByGenreData = fetchedMovies.reduce((acc, curr) => { - acc[curr.genre] = curr.movies; - return acc; - }, {}); - - setMoviesByGenre(moviesByGenreData); - } catch (error) { - console.error('Error fetching movies by genres:', error); - } - }; - - fetchMoviesByGenres(); - }, []); - - useEffect(() => { const fetchOTHERMovies = async () => { try { - const fetchedMovies = await getPopularMovies(); + // Create an array of promises for parallel fetching + const moviePromises = [ + getPopularMovies(), + getMoviesByGenre(53), // Thriller + getMoviesByGenre(35), // Comedy + getMoviesByGenre(28), // Romance + ]; + + // Await all promises to resolve in parallel + const [fetchedMovies, fetchedThrillerMovies, fetchedComedyMovies, fetchedRomanceMovies] = await Promise.all(moviePromises); + + // Update the respective states after fetching setMovies1(fetchedMovies); - - const fetchedThrillerMovies = await getMoviesByGenre(53); setThrillerMovies(fetchedThrillerMovies); - - const fetchedComedyMovies = await getMoviesByGenre(35); setComedyMovies(fetchedComedyMovies); - - const fetchedRomanceMovies = await getMoviesByGenre(28); setRomanceMovies(fetchedRomanceMovies); - + } catch (error) { console.error('Error fetching movies:', error); - } finally { - } }; - - fetchOTHERMovies(); + + fetchOTHERMovies(); }, []); + const shuffleArray = (array) => { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); @@ -204,29 +191,25 @@ const Home = ({ route }) => { try { let moviesData = await getMovies(); moviesData = shuffleArray(moviesData); // Shuffle the movies array - setMovies([{ key: "empty-left" }, ...moviesData, { key: "empty-right" }]); + setMovies([{ key: "empty-left" }, ...moviesData.slice(0, 9), { key: "empty-right" }]); } catch (error) { console.error("Failed to fetch movies:", error); } }; - + fetchMovies(); }, []); + + useEffect(() => { + const fetchUserWatchlists = async () => { try { const userId = userInfo.userId; - let userWatchlists = await getUserWatchlists(userId); - - //let userWatchlists = await getFollowedUsersWatchlists(userId); - - - // Remove duplicates based on watchlist IDs - // userWatchlists = userWatchlists.filter((watchlist, index, self) => - // index === self.findIndex((w) => w.id === watchlist.id) - // ); - + let userWatchlists = await getFollowedUsersWatchlists(userId); + + setWatchlists(userWatchlists); } catch (error) { console.error('Error fetching user watchlists:', error); @@ -237,10 +220,48 @@ const Home = ({ route }) => { fetchUserWatchlists(); }, []); + useEffect(() => { + const fetchMoviesByGenres = async () => { + try { + const genreMoviesPromises = Object.entries(genres).map(([genreName, genreId]) => + getNewMovies(genreId) + ); + + const fetchedMovies = await Promise.all(genreMoviesPromises); + + const moviesByGenreData = Object.keys(genres).reduce((acc, genreName, index) => { + acc[genreName] = fetchedMovies[index]; + return acc; + }, {}); + + setMoviesByGenre(moviesByGenreData); + } catch (error) { + console.error('Error fetching movies by genres:', error); + } + }; + + fetchMoviesByGenres(); + }, []); + + + + useEffect(() => { + const fetchMovies = async () => { + try { + const moviesData = await getMovies(); + setMovies([{ key: "empty-left" }, ...moviesData, { key: "empty-right" }]); + } catch (error) { + console.error("Failed to fetch movies:", error); + } + }; + + fetchMovies(); + }, []); + // useEffect(() => { // let interval; - + // if (isAutoScrolling) { // interval = setInterval(() => { // if (activeIndex === 9) { @@ -262,7 +283,7 @@ const Home = ({ route }) => { // } // }, 2000); // Adjust the interval as needed // } - + // return () => clearInterval(interval); // }, [isAutoScrolling, activeIndex, movies1]); // const getItemLayout = (data, index) => ({ @@ -305,7 +326,7 @@ const Home = ({ route }) => { return ; } - + const homeStyles = StyleSheet.create({ container: { @@ -333,6 +354,13 @@ const Home = ({ route }) => { overview={item.overview} rating={item.vote_average.toFixed(1)} date={new Date(item.release_date).getFullYear()} + imageComponent={ + + } /> ); @@ -381,7 +409,7 @@ const Home = ({ route }) => { return ( - navigation.navigate("MovieDescriptionPage", { userInfo: userInfo, ...movieDetails })}> + navigation.navigate("MovieDescriptionPage", { ...movieDetails, userInfo })}> { {item.description} - navigation.navigate("MovieDescriptionPage", { userInfo: userInfo, ...movieDetails })}> + navigation.navigate("MovieDescriptionPage", { ...movieDetails })}> Read more @@ -428,7 +456,7 @@ const Home = ({ route }) => { - {comedyMovies.slice(5, 24).map((movie, index) => ( + {comedyMovies.slice(5, 16).map((movie, index) => ( { - {watchlists.map((watchlist) => ( - goToWatchlistDetails(watchlist)}> - + {watchlists.map((watchlist, index) => ( + goToWatchlistDetails(watchlist)}> + {loading && ( + + )} + setLoading(false)}/> ( -// goToWatchlistDetails(watchlist)}> -// -// -// {watchlist.name} + }} numberOfLines={1} // Limits the text to 1 line + ellipsizeMode="tail" + >{watchlist.name} - + ))} @@ -501,7 +528,7 @@ const Home = ({ route }) => { - {romanceMovies.slice(0, 20).map((movie, index) => ( + {romanceMovies.slice(0, 10).map((movie, index) => ( { - {moviesByGenre[genreName]?.slice(0, 20).map((movie, index) => ( + {moviesByGenre[genreName]?.slice(0, 10).map((movie, index) => ( { setRefreshing(true); + renderTabContent(); fetchData().finally(() => setRefreshing(false)); }; @@ -176,11 +177,11 @@ export default function ProfilePage({ route }) { const renderTabContent = () => { switch (activeTab) { case 'posts': - return ; + return ; case 'likes': - return ; + return ; case 'watchlist': - return ; + return ; default: return null; } @@ -196,7 +197,7 @@ export default function ProfilePage({ route }) { return ( - } scrollbarThumbColor="rgba(0, 0, 0, 0)" showsVerticalScrollIndicator={false} > + } scrollbarThumbColor="rgba(0, 0, 0, 0)" showsVerticalScrollIndicator={false} > { useEffect(() => { const fetchPosters = async () => { const posters = {}; - const usedPosters = new Set(); // set of posters that have been used + const usedPosters = new Set(); + const cachedPosters = await AsyncStorage.getItem('cachedPosters'); + const parsedCachedPosters = cachedPosters ? JSON.parse(cachedPosters) : {}; + for (const [genreId, genreName] of sortedGenres) { + if (parsedCachedPosters[genreName]) { + posters[genreName] = parsedCachedPosters[genreName]; + continue; + } + const movies = await getMoviesByGenre(genreId); if (movies.length > 0) { let posterPath = `https://image.tmdb.org/t/p/w500${movies[0].poster_path}`; let index = 0; - // Ensure the poster is unique while (usedPosters.has(posterPath) && index < movies.length) { index++; - posterPath = `https://image.tmdb.org/t/p/w500${movies[index].poster_path}`; + if (index < movies.length) { + posterPath = `https://image.tmdb.org/t/p/w500${movies[index].poster_path}`; + } else { + break; + } } usedPosters.add(posterPath); posters[genreName] = posterPath; + + parsedCachedPosters[genreName] = posterPath; } } + + await AsyncStorage.setItem('cachedPosters', JSON.stringify(parsedCachedPosters)); setGenrePosters(posters); }; + fetchPosters(); }, []); @@ -237,6 +254,7 @@ const SearchPage = ({ route }) => { backgroundColor: "#00000080", flex: 1, textAlignVertical: "center", + justifyContent: "center", }, grid: {