api: fetch shows implemented

This commit is contained in:
Cron1cle
2025-10-02 13:24:38 +02:00
parent 921a53a504
commit badf355a2d
5 changed files with 152 additions and 30 deletions

56
apis/showApi.ts Normal file
View File

@@ -0,0 +1,56 @@
export type RawShow = {
showId: number;
title: string;
description: string;
genre: string;
thumbnailUrl: string;
bannerUrl?: string;
concept: string;
streamingServices: string;
startDate?: string;
endDate?: string | null;
};
export type Show = {
id: number;
title: string;
description: string;
genres: string[];
thumbnailUri: string;
bannerUri: string;
streamingService: string;
concept: string;
startDate?: string;
endDate?: string | null;
};
const API_URL = "http://45.157.177.99:8080/shows";
export async function getShows(): Promise<Show[]> {
try {
const response = await fetch(API_URL);
if (!response.ok) {
throw new Error("Network response was not ok");
}
const data: unknown = await response.json();
if (!Array.isArray(data)) {
console.warn("Expected array, got:", data);
return [];
}
return (data as RawShow[]).map((s) => ({
id: s.showId,
title: s.title,
description: s.description,
genres: s.genre ? s.genre.split(",").map((g) => g.trim()) : [],
thumbnailUri: s.thumbnailUrl,
bannerUri: s.bannerUrl ?? "",
streamingService: s.streamingServices,
concept: s.concept,
startDate: s.startDate,
endDate: s.endDate ?? null,
}));
} catch (error) {
console.error("Fetch error:", error);
throw error;
}
}

View File

@@ -1,24 +1,44 @@
import styles from "@/app/styles/indexStyles";
import ShowCard from "@/components/ui/ShowCard";
import { useShowContext } from "@/contexts/ShowContext";
import { router } from "expo-router";
import React from "react";
import { Text, View } from "react-native";
import {
GestureHandlerRootView,
ScrollView,
} from "react-native-gesture-handler";
export default function HomeScreen() {
const { shows } = useShowContext();
return (
<GestureHandlerRootView>
<View style={styles.mainContainer}>
<View style={styles.header}>
<Text style={styles.title}>FLTR</Text>
</View>
<ScrollView contentContainerStyle={{ paddingBottom: 30 }}>
{shows.map((show) => {
const showLiveBadge = show.endDate === null;
return (
<ShowCard
key={show.id}
onPress={() => router.push("/showDetails")}
imageUri="https://streamcoimg-a.akamaihd.net/000/123/147/123147-Banner-L2-b81d3e6b4df34af2887f701eec382f87.jpg"
streamingServiceUri="https://play-lh.googleusercontent.com/e8u4F0ED6hDMzmjg5cV_C5Sxrzr3xECniwKCD2Q8QfUeVMVRLG41TrsnqroTE7uxk4E=w240-h480-rw"
liveBadgeText="LIVE"
liveBadgeContainerStyle={styles.liveBadgeContainer}
genres={["Reality", "Dating"]}
imageUri={show.bannerUri || show.thumbnailUri}
streamingServiceUri={show.streamingService}
genres={show.genres}
{...(showLiveBadge
? {
liveBadgeText: "LIVE",
liveBadgeContainerStyle: styles.liveBadgeContainer,
}
: {})}
/>
);
})}
</ScrollView>
</View>
</GestureHandlerRootView>
);
}

View File

@@ -1,8 +1,10 @@
import { ShowProvider } from "@/contexts/ShowContext";
import { Stack } from "expo-router";
import "react-native-reanimated";
export default function RootLayout() {
return (
<ShowProvider>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
@@ -12,5 +14,6 @@ export default function RootLayout() {
}}
/>
</Stack>
</ShowProvider>
);
}

View File

@@ -4,7 +4,7 @@ import { Image, StyleSheet, Text, TouchableOpacity, View } from "react-native";
type ShowCardProps = {
imageUri: string;
streamingServiceUri: string;
liveBadgeText: string;
liveBadgeText?: string;
liveBadgeContainerStyle?: object;
genres: string[];
onPress?: () => void;
@@ -33,15 +33,16 @@ const ShowCard = ({
<View style={styles.streamingServiceIcon}>
<Image
source={{
uri: streamingServiceUri,
uri: "https://play-lh.googleusercontent.com/e8u4F0ED6hDMzmjg5cV_C5Sxrzr3xECniwKCD2Q8QfUeVMVRLG41TrsnqroTE7uxk4E",
}}
style={[StyleSheet.absoluteFillObject, { borderRadius: 15 }]}
/>
</View>
{liveBadgeText && (
<View style={liveBadgeContainerStyle}>
<Text style={styles.liveBadgeText}>{liveBadgeText}</Text>
</View>
)}
<View style={styles.genreSection}>
{genres.map((genre) => (
<Text key={genre} style={styles.genreLabel}>
@@ -112,7 +113,7 @@ const styles = StyleSheet.create({
paddingHorizontal: 10,
borderRadius: 10,
fontStyle: "italic",
backgroundColor: "rgba(255, 255, 255, 0.1)",
backgroundColor: "rgba(255, 255, 255, 1)",
overflow: "hidden",
},
});

42
contexts/ShowContext.tsx Normal file
View File

@@ -0,0 +1,42 @@
import { getShows, Show } from "@/apis/showApi";
import { createContext, useContext, useEffect, useState } from "react";
type ShowContextType = {
shows: Show[];
loading: boolean;
error: string | null;
};
const ShowContext = createContext<ShowContextType | null>(null);
export const ShowProvider = ({ children }: { children: React.ReactNode }) => {
const [shows, setShows] = useState<Show[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
(async () => {
try {
const data = await getShows();
setShows(data);
} catch {
setError("Failed to fetch shows");
} finally {
setLoading(false);
}
})();
}, []);
return (
<ShowContext.Provider value={{ shows, loading, error }}>
{children}
</ShowContext.Provider>
);
};
export const useShowContext = () => {
const ctx = useContext(ShowContext);
if (!ctx)
throw new Error("useShowContext must be used within a ShowProvider");
return ctx;
};