This commit is contained in:
Cron1cle
2025-10-09 17:20:40 +02:00
parent c116352310
commit aedd87416f
10 changed files with 141 additions and 50 deletions

View File

@@ -11,6 +11,12 @@ export type PersonHistoryEntry = {
showId: number; showId: number;
seasonId: number; seasonId: number;
seasonNumber: number; seasonNumber: number;
partner?: {
id: number;
name: string;
birthDate?: string;
imageUri: string | null;
} | null;
}; };
const PERSON_API_BASE = "http://45.157.177.99:8080/persons"; const PERSON_API_BASE = "http://45.157.177.99:8080/persons";
@@ -28,11 +34,25 @@ export async function getPersonHistory(
console.warn("History expected array, got:", data); console.warn("History expected array, got:", data);
return []; return [];
} }
return (data as PersonHistorySeasonRaw[]).map((s) => ({ return (data as PersonHistorySeasonRaw[]).map((season) => {
showId: s.show, const me = season.seasonParticipants.find(
seasonId: s.seasonId, (p) => p.person.personId === personId
seasonNumber: s.seasonNumber, );
})); let partner: PersonHistoryEntry["partner"] = null;
if (me?.partner?.person) {
partner = {
id: me.partner.person.personId,
name: me.partner.person.name,
imageUri: me.partner.person.imageUrl ?? null,
};
}
return {
showId: season.show,
seasonId: season.seasonId,
seasonNumber: season.seasonNumber,
partner,
};
});
} catch (e) { } catch (e) {
console.error("getPersonHistory error:", e); console.error("getPersonHistory error:", e);
return []; return [];

View File

@@ -58,7 +58,7 @@ export async function getSeason(
: undefined, : undefined,
imageUri: imageUri:
p.person.imageUrl ?? p.person.imageUrl ??
"https://via.placeholder.com/300x400.png?text=No+Image", `https://i.pravatar.cc/300?img=${Math.random() * 70}`,
}) })
); );
return { return {

View File

@@ -52,6 +52,8 @@ export async function getShows(): Promise<Show[]> {
concept: s.concept, concept: s.concept,
running: s.running, running: s.running,
logoUri: s.logoUrl ?? "", logoUri: s.logoUrl ?? "",
startDate: s.startDate,
endDate: s.endDate,
})); }));
} catch (error) { } catch (error) {
console.error("Fetch error:", error); console.error("Fetch error:", error);

View File

@@ -99,7 +99,7 @@ export default function HomeScreen() {
<TouchableOpacity <TouchableOpacity
key={serviceName} key={serviceName}
style={{ style={{
padding: 3, padding: 2,
backgroundColor: "hsl(221, 39%, 80%)", backgroundColor: "hsl(221, 39%, 80%)",
borderRadius: 50, borderRadius: 50,
}} }}

View File

@@ -4,12 +4,12 @@ import Ionicons from "@expo/vector-icons/Ionicons";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { usePersonContext } from "@/contexts/PersonContext"; import { usePersonContext } from "@/contexts/PersonContext";
import * as WebBrowser from "expo-web-browser";
import { import {
ScrollView, ScrollView,
GestureHandlerRootView, GestureHandlerRootView,
} from "react-native-gesture-handler"; } from "react-native-gesture-handler";
import { useShowContext } from "@/contexts/ShowContext"; import { useShowContext } from "@/contexts/ShowContext";
import { getPersonHistory } from "@/apis/personApi";
export default function ParticipantScreen() { export default function ParticipantScreen() {
const { getPersonAppearances, isLoading, getError } = usePersonContext(); const { getPersonAppearances, isLoading, getError } = usePersonContext();
@@ -34,8 +34,9 @@ export default function ParticipantScreen() {
const grouped = data.showIds.map((id) => ({ const grouped = data.showIds.map((id) => ({
showId: id, showId: id,
seasons: data.byShow[id], seasons: data.byShow[id],
partners: data.partnersByShow[id] || [],
})); }));
setAppearances(grouped); setAppearances(grouped as any);
})(); })();
return () => { return () => {
active = false; active = false;
@@ -44,14 +45,30 @@ export default function ParticipantScreen() {
const resolved = useMemo( const resolved = useMemo(
() => () =>
appearances (appearances as any[])
.map((a) => { .map((a) => {
const show = shows.find((s) => s.id === a.showId); const show = shows.find((s) => s.id === a.showId);
if (!show) return null; if (!show) return null;
return { show, seasons: a.seasons }; return {
show,
seasons: a.seasons as number[],
partners: a.partners as {
seasonNumber: number;
partner?: { id: number; name: string; imageUrl?: string | null };
}[],
};
}) })
.filter( .filter(
(v): v is { show: (typeof shows)[number]; seasons: number[] } => !!v (
v
): v is {
show: (typeof shows)[number];
seasons: number[];
partners: {
seasonNumber: number;
partner?: { id: number; name: string; imageUrl?: string | null };
}[];
} => !!v
), ),
[appearances, shows] [appearances, shows]
); );
@@ -75,12 +92,6 @@ export default function ParticipantScreen() {
<View style={styles.dot} /> <View style={styles.dot} />
<Text style={styles.participantInfo}>Köln</Text> <Text style={styles.participantInfo}>Köln</Text>
</View> </View>
<Image
source={{
uri: "https://www.fernseh-puls.com/wp-content/uploads/are-you-the-one-calvin-o-im-steckbrief-wir-stellen-euch-den-kandidaten-vor.jpg",
}}
style={styles.participantImage}
/>
<View style={styles.performedShowsSection}> <View style={styles.performedShowsSection}>
<Text style={styles.performedShowsTitle}>Auftritte:</Text> <Text style={styles.performedShowsTitle}>Auftritte:</Text>
@@ -107,7 +118,13 @@ export default function ParticipantScreen() {
marginTop: 15, marginTop: 15,
}} }}
> >
{resolved.map(({ show, seasons }) => ( {resolved.map(({ show, seasons, partners }) => {
const seasonPartnerLines = partners.map((p) => {
const label = `Staffel ${p.seasonNumber}`;
if (!p.partner) return label;
return `${label} • Partner: ${p.partner.name}`;
});
return (
<TouchableOpacity key={show.id} style={styles.showContainer}> <TouchableOpacity key={show.id} style={styles.showContainer}>
<Image <Image
source={{ uri: show.thumbnailUri }} source={{ uri: show.thumbnailUri }}
@@ -116,14 +133,16 @@ export default function ParticipantScreen() {
<Text style={styles.showTitle} numberOfLines={2}> <Text style={styles.showTitle} numberOfLines={2}>
{show.title} {show.title}
</Text> </Text>
<Text style={styles.showSeason} numberOfLines={1}>
Staffel <Text
{seasons.length === 1 style={styles.showSeason}
? ` ${seasons[0]}` numberOfLines={seasonPartnerLines.length}
: `n ${seasons.join(", ")}`} >
{seasonPartnerLines.join("\n")}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
))} );
})}
</ScrollView> </ScrollView>
</View> </View>
</ScrollView> </ScrollView>

View File

@@ -4,6 +4,7 @@ import ShowInfo from "@/components/ui/ShowInfo";
import ParticipantDetails from "@/components/ParticipantDeatails"; import ParticipantDetails from "@/components/ParticipantDeatails";
import React from "react"; import React from "react";
import { useSeasonContext } from "@/contexts/SeasonContext"; import { useSeasonContext } from "@/contexts/SeasonContext";
import { getShowColors } from "@/constants/colors";
import { import {
Dimensions, Dimensions,
Image, Image,
@@ -12,7 +13,6 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
} from "react-native"; } from "react-native";
import * as WebBrowser from "expo-web-browser";
import styles from "./stackStyles/showDetailStyles"; import styles from "./stackStyles/showDetailStyles";
export default function ShowDetails() { export default function ShowDetails() {
@@ -23,6 +23,8 @@ export default function ShowDetails() {
genres, genres,
streamingService, streamingService,
id, id,
startDate,
endDate,
logoUri, logoUri,
} = useLocalSearchParams(); } = useLocalSearchParams();
const [selectedParticipants, setSelectedParticipants] = const [selectedParticipants, setSelectedParticipants] =
@@ -37,6 +39,8 @@ export default function ShowDetails() {
const [pLoading, setPLoading] = React.useState(false); const [pLoading, setPLoading] = React.useState(false);
const [pError, setPError] = React.useState<string | null>(null); const [pError, setPError] = React.useState<string | null>(null);
const { tabColor, seasonColor } = getShowColors(Number(id));
React.useEffect(() => { React.useEffect(() => {
if (!showId) return; if (!showId) return;
let active = true; let active = true;
@@ -85,6 +89,8 @@ export default function ShowDetails() {
seasons={seasonCount} seasons={seasonCount}
participants={participants.length} participants={participants.length}
streamingService={streamingService as string} streamingService={streamingService as string}
startDate={startDate as string}
endDate={endDate as string | null}
/> />
<View style={styles.showBannerLogoContainer}> <View style={styles.showBannerLogoContainer}>
@@ -103,7 +109,7 @@ export default function ShowDetails() {
styles.infoLabel, styles.infoLabel,
{ {
fontWeight: selectedParticipants ? "bold" : "normal", fontWeight: selectedParticipants ? "bold" : "normal",
color: selectedParticipants ? "#199edb" : "hsl(0, 0%, 65%)", color: selectedParticipants ? tabColor : "hsl(0, 0%, 65%)",
}, },
]} ]}
> >
@@ -142,7 +148,7 @@ export default function ShowDetails() {
{ {
backgroundColor: backgroundColor:
selectedSeason === season selectedSeason === season
? "#199edb" ? seasonColor
: "hsl(0, 0%, 20%)", : "hsl(0, 0%, 20%)",
}, },
]} ]}
@@ -172,7 +178,10 @@ export default function ShowDetails() {
{participants.map((p) => ( {participants.map((p) => (
<TouchableOpacity <TouchableOpacity
key={p.id} key={p.id}
style={styles.participantContainer} style={[
styles.participantContainer,
{ backgroundColor: tabColor },
]}
onPress={() => onPress={() =>
router.push({ router.push({
pathname: "/participant", pathname: "/participant",
@@ -190,6 +199,8 @@ export default function ShowDetails() {
height: "100%", height: "100%",
borderRadius: 10, borderRadius: 10,
}} }}
resizeMode="cover"
blurRadius={p.imageUri.includes("pravatar") ? 16 : 0}
/> />
<Text style={styles.participantLabel} numberOfLines={2}> <Text style={styles.participantLabel} numberOfLines={2}>
{p.name} {p.name}

View File

@@ -50,7 +50,7 @@ const styles = StyleSheet.create({
}, },
performedShowsSection: { performedShowsSection: {
width: "100%", width: "100%",
height: 375, height: "100%",
paddingLeft: 15, paddingLeft: 15,
paddingBottom: 20, paddingBottom: 20,
backgroundColor: "hsl(221, 39%, 0%)", backgroundColor: "hsl(221, 39%, 0%)",

View File

@@ -4,12 +4,16 @@ type ShowInfoProps = {
seasons: number; seasons: number;
participants: number; participants: number;
streamingService: string; streamingService: string;
startDate: string;
endDate?: string | null;
}; };
const ShowInfo = ({ const ShowInfo = ({
seasons, seasons,
participants, participants,
streamingService, streamingService,
startDate,
endDate,
}: ShowInfoProps) => { }: ShowInfoProps) => {
return ( return (
<View style={styles.showMainInfoSection}> <View style={styles.showMainInfoSection}>

20
constants/colors.ts Normal file
View File

@@ -0,0 +1,20 @@
export type ShowDetailColors = {
tabColor: string;
seasonColor: string;
};
export const ShowColors: Record<number, ShowDetailColors> = {
1: { tabColor: "#009fe2", seasonColor: "#ec5691" },
2: { tabColor: "#ffffff", seasonColor: "#3fa8f3" },
3: { tabColor: "#ffffff", seasonColor: "#000000" },
4: { tabColor: "#8fb4f3", seasonColor: "#4b046c" },
5: { tabColor: "#ffffff", seasonColor: "#000000" },
6: { tabColor: "#ffffff", seasonColor: "#ff0000" },
7: { tabColor: "#25322c", seasonColor: "#957a45" },
8: { tabColor: "#ffffff", seasonColor: "#0179b5" },
};
export function getShowColors(showId: number): ShowDetailColors {
if (!showId) return { tabColor: "#009fe2", seasonColor: "#ec5691" };
return ShowColors[showId] || { tabColor: "#009fe2", seasonColor: "#ec5691" };
}

View File

@@ -11,6 +11,10 @@ type PersonAppearances = {
raw: PersonHistoryEntry[]; raw: PersonHistoryEntry[];
byShow: Record<number, number[]>; byShow: Record<number, number[]>;
showIds: number[]; showIds: number[];
partnersByShow: Record<
number,
{ seasonNumber: number; partner?: PersonHistoryEntry["partner"] }[]
>;
}; };
type PersonContextType = { type PersonContextType = {
@@ -32,23 +36,34 @@ export const PersonProvider = ({ children }: { children: ReactNode }) => {
const buildAppearances = ( const buildAppearances = (
entries: PersonHistoryEntry[] entries: PersonHistoryEntry[]
): PersonAppearances => { ): PersonAppearances => {
const byShow: Record<number, Set<number>> = {}; const byShowSet: Record<number, Set<number>> = {};
const partnersByShow: PersonAppearances["partnersByShow"] = {};
for (const e of entries) { for (const e of entries) {
if (!byShow[e.showId]) byShow[e.showId] = new Set(); if (!byShowSet[e.showId]) byShowSet[e.showId] = new Set();
byShow[e.showId].add(e.seasonNumber); byShowSet[e.showId].add(e.seasonNumber);
if (!partnersByShow[e.showId]) partnersByShow[e.showId] = [];
partnersByShow[e.showId].push({
seasonNumber: e.seasonNumber,
partner: e.partner ?? undefined,
});
} }
const byShowSorted: Record<number, number[]> = Object.fromEntries( const byShow: Record<number, number[]> = Object.fromEntries(
Object.entries(byShow).map(([showId, seasonsSet]) => [ Object.entries(byShowSet).map(([showId, seasons]) => [
Number(showId), Number(showId),
Array.from(seasonsSet).sort((a, b) => a - b), Array.from(seasons).sort((a, b) => a - b),
]) ])
); );
Object.values(partnersByShow).forEach((arr) =>
arr.sort((a, b) => a.seasonNumber - b.seasonNumber)
);
return { return {
raw: entries, raw: entries,
byShow: byShowSorted, byShow,
showIds: Object.keys(byShowSorted) showIds: Object.keys(byShow)
.map(Number) .map(Number)
.sort((a, b) => a - b), .sort((a, b) => a - b),
partnersByShow,
}; };
}; };
@@ -65,7 +80,7 @@ export const PersonProvider = ({ children }: { children: ReactNode }) => {
...err, ...err,
[personId]: e?.message || "Fehler beim Laden", [personId]: e?.message || "Fehler beim Laden",
})); }));
return { raw: [], byShow: {}, showIds: [] }; return { raw: [], byShow: {}, showIds: [], partnersByShow: {} };
} finally { } finally {
setLoading((l) => ({ ...l, [personId]: false })); setLoading((l) => ({ ...l, [personId]: false }));
} }
@@ -90,7 +105,7 @@ export const PersonProvider = ({ children }: { children: ReactNode }) => {
const getSeasonsForShow = useCallback( const getSeasonsForShow = useCallback(
async (personId: number, showId: number) => { async (personId: number, showId: number) => {
const app = await getPersonAppearances(personId); const app = await getPersonAppearances(personId);
return app.byShow[showId] || []; return (app.byShow as Record<number, number[]>)[showId] || [];
}, },
[getPersonAppearances] [getPersonAppearances]
); );