api: add Person and StreamingService contexts
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import styles from "@/app/tabStyles/indexStyles";
|
||||
import ShowCard from "@/components/ui/ShowCard";
|
||||
import { useShowContext } from "@/contexts/ShowContext";
|
||||
import { useStreamingServiceContext } from "@/contexts/StreamingServiceContext";
|
||||
import { router } from "expo-router";
|
||||
import React from "react";
|
||||
import { ActivityIndicator, Text, View } from "react-native";
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
|
||||
export default function HomeScreen() {
|
||||
const { shows, error, loading } = useShowContext();
|
||||
const { streamingServices } = useStreamingServiceContext();
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -47,6 +49,11 @@ export default function HomeScreen() {
|
||||
<ScrollView contentContainerStyle={{ paddingBottom: 30 }}>
|
||||
{shows.map((show) => {
|
||||
const showLiveBadge = show.running;
|
||||
const streamingService =
|
||||
streamingServices[
|
||||
`assets.images.streamingServices.${show.streamingService.toLowerCase()}`
|
||||
];
|
||||
|
||||
return (
|
||||
<ShowCard
|
||||
key={show.id}
|
||||
@@ -66,7 +73,7 @@ export default function HomeScreen() {
|
||||
})
|
||||
}
|
||||
imageUri={show.bannerUri}
|
||||
streamingServiceUri={show.streamingService}
|
||||
streamingServiceUri={streamingService}
|
||||
genres={show.genres}
|
||||
{...(showLiveBadge
|
||||
? {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ShowProvider } from "@/contexts/ShowContext";
|
||||
import { SeasonProvider } from "@/contexts/SeasonContext";
|
||||
import { StreamingServiceProvider } from "@/contexts/StreamingServiceContext";
|
||||
import { PersonProvider } from "@/contexts/PersonContext";
|
||||
import { Stack } from "expo-router";
|
||||
import "react-native-reanimated";
|
||||
|
||||
@@ -7,22 +9,26 @@ export default function RootLayout() {
|
||||
return (
|
||||
<ShowProvider>
|
||||
<SeasonProvider>
|
||||
<Stack>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen
|
||||
name="showDetails"
|
||||
options={{
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="participant"
|
||||
options={{
|
||||
presentation: "fullScreenModal",
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<StreamingServiceProvider>
|
||||
<PersonProvider>
|
||||
<Stack>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen
|
||||
name="showDetails"
|
||||
options={{
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="participant"
|
||||
options={{
|
||||
presentation: "fullScreenModal",
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</PersonProvider>
|
||||
</StreamingServiceProvider>
|
||||
</SeasonProvider>
|
||||
</ShowProvider>
|
||||
);
|
||||
|
||||
@@ -1,80 +1,67 @@
|
||||
import { View, Image, Text, TouchableOpacity } from "react-native";
|
||||
import styles from "@/app/stackStyles/participantStyles";
|
||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||
import React, {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { router } from "expo-router";
|
||||
import Feather from "@expo/vector-icons/Feather";
|
||||
import BottomSheet, { BottomSheetScrollView } from "@gorhom/bottom-sheet";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { usePersonContext } from "@/contexts/PersonContext";
|
||||
import {
|
||||
ScrollView,
|
||||
GestureHandlerRootView,
|
||||
} from "react-native-gesture-handler";
|
||||
import { useShowContext } from "@/contexts/ShowContext";
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withTiming,
|
||||
withRepeat,
|
||||
withSequence,
|
||||
Easing,
|
||||
cancelAnimation,
|
||||
} from "react-native-reanimated";
|
||||
import { getPersonHistory } from "@/apis/personApi";
|
||||
|
||||
export default function ParticipantScreen() {
|
||||
const { getPersonAppearances, isLoading, getError } = usePersonContext();
|
||||
const [appearances, setAppearances] = useState<
|
||||
{
|
||||
showId: number;
|
||||
seasons: number[];
|
||||
}[]
|
||||
>([]);
|
||||
const { shows, error, loading } = useShowContext();
|
||||
|
||||
const bottomSheetRef = useRef<BottomSheet>(null);
|
||||
|
||||
const [sheetIndex, setSheetIndex] = useState(1);
|
||||
|
||||
const handleSheetChange = useCallback((index: number) => {
|
||||
setSheetIndex(index);
|
||||
}, []);
|
||||
|
||||
const snapPoints = useMemo(() => ["10%", "10%", "45%"], []);
|
||||
|
||||
const bounce = useSharedValue(0);
|
||||
const expanded = useSharedValue(0);
|
||||
const { participantId, name, season } = useLocalSearchParams();
|
||||
const numericId = Array.isArray(participantId)
|
||||
? Number(participantId[0])
|
||||
: Number(participantId);
|
||||
|
||||
useEffect(() => {
|
||||
if (sheetIndex === 2) {
|
||||
cancelAnimation(bounce);
|
||||
expanded.value = withTiming(1, { duration: 120 });
|
||||
bounce.value = withTiming(-12, { duration: 120 });
|
||||
} else {
|
||||
expanded.value = withTiming(0, { duration: 100 });
|
||||
bounce.value = withRepeat(
|
||||
withSequence(
|
||||
withTiming(-6, { duration: 250, easing: Easing.out(Easing.quad) }),
|
||||
withTiming(0, { duration: 250, easing: Easing.inOut(Easing.quad) })
|
||||
),
|
||||
-1,
|
||||
true
|
||||
);
|
||||
}
|
||||
let active = true;
|
||||
(async () => {
|
||||
if (!numericId || Number.isNaN(numericId)) return;
|
||||
const data = await getPersonAppearances(numericId);
|
||||
if (!active) return;
|
||||
const grouped = data.showIds.map((id) => ({
|
||||
showId: id,
|
||||
seasons: data.byShow[id],
|
||||
}));
|
||||
setAppearances(grouped);
|
||||
})();
|
||||
return () => {
|
||||
cancelAnimation(bounce);
|
||||
active = false;
|
||||
};
|
||||
}, [sheetIndex, bounce, expanded]);
|
||||
}, [numericId, getPersonAppearances]);
|
||||
|
||||
const iconAnimatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [
|
||||
{ translateY: bounce.value },
|
||||
{ rotate: `${expanded.value * 180}deg` },
|
||||
],
|
||||
opacity: 1 - expanded.value * 0.3,
|
||||
}));
|
||||
const resolved = useMemo(
|
||||
() =>
|
||||
appearances
|
||||
.map((a) => {
|
||||
const show = shows.find((s) => s.id === a.showId);
|
||||
if (!show) return null;
|
||||
return { show, seasons: a.seasons };
|
||||
})
|
||||
.filter(
|
||||
(v): v is { show: (typeof shows)[number]; seasons: number[] } => !!v
|
||||
),
|
||||
[appearances, shows]
|
||||
);
|
||||
|
||||
return (
|
||||
<GestureHandlerRootView style={styles.mainContainer}>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
<Text style={styles.participantName}>Calvin Ogara</Text>
|
||||
<Text style={styles.participantName}>
|
||||
{name ? (Array.isArray(name) ? name[0] : name) : "Teilnehmer"}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.closeIcon}
|
||||
onPress={() => router.back()}
|
||||
@@ -97,7 +84,21 @@ export default function ParticipantScreen() {
|
||||
|
||||
<View style={styles.performedShowsSection}>
|
||||
<Text style={styles.performedShowsTitle}>Auftritte:</Text>
|
||||
|
||||
{isLoading(numericId) && (
|
||||
<Text style={{ color: "white", marginTop: 8 }}>Lädt...</Text>
|
||||
)}
|
||||
{getError(numericId) && (
|
||||
<Text style={{ color: "tomato", marginTop: 8 }}>
|
||||
{getError(numericId)}
|
||||
</Text>
|
||||
)}
|
||||
{!isLoading(numericId) &&
|
||||
resolved.length === 0 &&
|
||||
!getError(numericId) && (
|
||||
<Text style={{ color: "gray", marginTop: 8 }}>
|
||||
Keine Einträge.
|
||||
</Text>
|
||||
)}
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
@@ -106,38 +107,25 @@ export default function ParticipantScreen() {
|
||||
marginTop: 15,
|
||||
}}
|
||||
>
|
||||
{shows.map((show, i) => (
|
||||
<View style={styles.showContainer} key={i}>
|
||||
{resolved.map(({ show, seasons }) => (
|
||||
<TouchableOpacity key={show.id} style={styles.showContainer}>
|
||||
<Image
|
||||
source={{ uri: show.thumbnailUri }}
|
||||
style={styles.showImage}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.showTitle} numberOfLines={2}>
|
||||
{show.title}
|
||||
</Text>
|
||||
<Text style={styles.showSeason} numberOfLines={1}>
|
||||
Staffel
|
||||
{seasons.length === 1
|
||||
? ` ${seasons[0]}`
|
||||
: `n ${seasons.join(", ")}`}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
<BottomSheet
|
||||
ref={bottomSheetRef}
|
||||
index={1}
|
||||
snapPoints={snapPoints}
|
||||
enableDynamicSizing={false}
|
||||
onChange={handleSheetChange}
|
||||
backgroundStyle={{ backgroundColor: "hsl(221, 39%, 12%)" }}
|
||||
handleIndicatorStyle={{ backgroundColor: "transparent" }}
|
||||
>
|
||||
<BottomSheetScrollView
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
{ alignSelf: "center", marginBottom: 20 },
|
||||
iconAnimatedStyle,
|
||||
]}
|
||||
>
|
||||
<Feather name="chevrons-up" size={40} color="white" />
|
||||
</Animated.View>
|
||||
</BottomSheetScrollView>
|
||||
</BottomSheet>
|
||||
</ScrollView>
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
|
||||
@@ -175,7 +175,10 @@ export default function ShowDetails() {
|
||||
onPress={() =>
|
||||
router.push({
|
||||
pathname: "/participant",
|
||||
params: { participantId: p.id, name: p.name },
|
||||
params: {
|
||||
participantId: p.id,
|
||||
name: p.name,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
|
||||
@@ -89,6 +89,20 @@ const styles = StyleSheet.create({
|
||||
margin: 6,
|
||||
backgroundColor: "#eee",
|
||||
},
|
||||
showTitle: {
|
||||
color: "white",
|
||||
fontSize: 12,
|
||||
fontWeight: "600",
|
||||
textAlign: "center",
|
||||
marginTop: 10,
|
||||
},
|
||||
showSeason: {
|
||||
color: "hsl(0, 0%, 80%)",
|
||||
fontSize: 12,
|
||||
fontWeight: "400",
|
||||
textAlign: "center",
|
||||
marginTop: 3,
|
||||
},
|
||||
});
|
||||
|
||||
export default styles;
|
||||
|
||||
Reference in New Issue
Block a user