updated search

This commit is contained in:
DevOFVictory
2025-10-23 18:49:01 +02:00
parent 0fbe217fce
commit 30dab9051a
6 changed files with 76 additions and 70 deletions

View File

@@ -1,7 +1,8 @@
import { AutoCompleteItem } from "@/apis/autoCompleteApi"; import { AutoCompleteItem } from "@/apis/autoCompleteApi";
import { getSearchResults, SearchResultItem } from "@/apis/searchApi"; import { getSearchResults, SearchResultItem } from "@/apis/searchApi";
import { Season } from "@/apis/seasonApi";
import { Show } from "@/apis/showApi";
import styles from "@/app/tabStyles/indexStyles"; import styles from "@/app/tabStyles/indexStyles";
import { Season, Show } from "@/app/types";
import ShowBox from "@/components/discovery/ShowBox"; import ShowBox from "@/components/discovery/ShowBox";
import { useDiscoveryContext } from "@/contexts/DiscoveryContext"; import { useDiscoveryContext } from "@/contexts/DiscoveryContext";
import { FontAwesome } from "@expo/vector-icons"; import { FontAwesome } from "@expo/vector-icons";
@@ -56,6 +57,8 @@ export default function ExploreScreen() {
} }
}, [results]); }, [results]);
// Group SEASON results by showId // Group SEASON results by showId
const seasonsByShowId = React.useMemo(() => { const seasonsByShowId = React.useMemo(() => {
const map = new Map<number, Season[]>(); const map = new Map<number, Season[]>();
@@ -84,24 +87,27 @@ export default function ExploreScreen() {
React.useEffect(() => { React.useEffect(() => {
const needed = Array.from(seasonsByShowId.keys()).filter((id) => !showsById[id]); const needed = Array.from(seasonsByShowId.keys()).filter((id) => !showsById[id]);
if (needed.length === 0) return; if (needed.length === 0) return;
let cancelled = false; let cancelled = false;
(async () => { (async () => {
try { try {
const fetched = await Promise.all(needed.map((id) => getShowById(id))); const fetched = await Promise.all(needed.map((id) => getShowById(id)));
if (cancelled) return; if (cancelled) return;
const next: Record<number, Show> = {}; const next: Record<number, Show> = {};
for (const s of fetched) { for (const s of fetched) {
if (!s) continue; if (!s) continue;
next[Number((s as any).showId)] = s as Show; next[s.id] = s; // wichtig: s.id, nicht s.showId
}
if (Object.keys(next).length) {
setShowsById((prev) => ({ ...prev, ...next }));
} }
if (Object.keys(next).length) setShowsById((prev) => ({ ...prev, ...next }));
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
})(); })();
return () => {
cancelled = true; return () => { cancelled = true; };
};
}, [seasonsByShowId, showsById]); }, [seasonsByShowId, showsById]);
// PERSON hits shown at the top (like old screen) // PERSON hits shown at the top (like old screen)
@@ -188,7 +194,7 @@ export default function ExploreScreen() {
<View style={{ width: "100%", paddingHorizontal: 10, marginBottom: 12 }}> <View style={{ width: "100%", paddingHorizontal: 10, marginBottom: 12 }}>
<Text style={{ color: "white", fontSize: 18, fontWeight: "600", marginBottom: 6 }}>Personen</Text> <Text style={{ color: "white", fontSize: 18, fontWeight: "600", marginBottom: 6 }}>Personen</Text>
{persons.slice(0, 5).map((p) => ( {persons.slice(0, 5).map((p) => (
<PersonRow key={`p-${p.personId ?? p.id}`} person={p} /> <PersonRow key={`p-${p.personId ?? p.id}`} person={p}/>
))} ))}
</View> </View>
)} )}
@@ -203,21 +209,21 @@ export default function ExploreScreen() {
// If show metadata is not yet loaded, render a minimal ShowBox fallback once per page item // If show metadata is not yet loaded, render a minimal ShowBox fallback once per page item
if (!show) { if (!show) {
return ( return (
<SeasonCarousel <SeasonCarousel
key={`sc-${showId}`} key={`sc-${showId}`}
show={{ showId: showId as any, title: "blaaa", description: "", genre: "", thumbnailUrl: "", running: false } as any} show={{ showId: showId as any, title: "blaaa", description: "", genre: "", thumbnailUrl: "", running: false } as any}
seasons={seasons} seasons={seasons}
renderItem={(s) => <ShowBox show={{ showId: showId as any, title: `Show #${showId}`, description: "", genre: "", thumbnailUrl: "", running: false } as any} displayedSeason={s} shadow={false} />} renderItem={(s) => <ShowBox show={{ showId: showId as any, title: `Show #${showId}`, description: "", genre: "", thumbnailUrl: "", running: false } as any} displayedSeason={s} shadow={false} />}
/> />
); );
} }
return ( return (
<SeasonCarousel <SeasonCarousel
key={`sc-${showId}`} key={`sc-${showId}`}
show={show} show={show}
seasons={seasons} seasons={seasons}
renderItem={(s) => <ShowBox show={show} displayedSeason={s} shadow={false} />} renderItem={(s) => <ShowBox show={show} displayedSeason={s} shadow={false} />}
/> />
); );
})} })}

View File

@@ -1,32 +0,0 @@
export type Person = {
personId: number;
name: string;
birthDate?: string | null;
imageUrl?: string | null;
};
export type Season = {
seasonId: number;
showId: number;
startDate?: string | null;
endDate?: string | null;
seasonNumber?: number | null;
participants?: Person[];
moderators?: Person[];
};
export type Show = {
showId: number;
title: string;
description?: string;
genre?: string;
thumbnailUrl: string;
logoUrl?: string;
bannerUrl?: string;
running?: boolean;
streamingServices?: string;
concept?: string
};

View File

@@ -1,5 +1,6 @@
import { FontAwesome } from "@expo/vector-icons"; import { FontAwesome } from "@expo/vector-icons";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { router } from "expo-router";
import React from "react"; import React from "react";
import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
@@ -16,16 +17,32 @@ const calcAge = (birthDate?: string | null): number | null => {
return age < 0 || age > 130 ? null : age; return age < 0 || age > 130 ? null : age;
}; };
export default function PersonRow({ person }: { person: PersonLite }) { type Props = {
person: any;
onPress?: () => void;
};
export default function PersonRow({ person, onPress }: Props) {
const navigation = useNavigation(); const navigation = useNavigation();
const age = calcAge(person.birthDate); const age = calcAge(person.birthDate);
const id = person.personId ?? person.id; const id = person.personId ?? person.id;
const goToPerson = React.useCallback(
(id: number) => {
console.log("go to person", id);
router.push({
pathname: "/participant",
params: { participantId: String(id), name: person.name },
});
}, []
);
return ( return (
<TouchableOpacity <TouchableOpacity
onPress={() => { onPress={() => {
// If your PersonDetail expects a Person object instead of an id, adapt this accordingly goToPerson(Number(id));
// navigation.navigate("PersonDetail" as never, { personId: id } as never);
}} }}
style={styles.personRow} style={styles.personRow}
> >
@@ -34,7 +51,7 @@ export default function PersonRow({ person }: { person: PersonLite }) {
</View> </View>
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
<Text style={styles.personName}>{person.name || "Unbekannt"}{age != null ? ` (${age})` : ""}</Text> <Text style={styles.personName}>{person.name || "Unbekannt"}{age != null ? ` (${age})` : ""}</Text>
<Text style={styles.personMeta}>aus: unterschiedlichen Shows</Text> {/* <Text style={styles.personMeta}>aus: unterschiedlichen Shows</Text> */}
</View> </View>
<FontAwesome name="chevron-right" size={14} color="#888" /> <FontAwesome name="chevron-right" size={14} color="#888" />
</TouchableOpacity> </TouchableOpacity>

View File

@@ -1,4 +1,5 @@
import { Season, Show } from "@/app/types"; import { Season } from "@/apis/seasonApi";
import { Show } from "@/apis/showApi";
import { FontAwesome } from "@expo/vector-icons"; import { FontAwesome } from "@expo/vector-icons";
import React from "react"; import React from "react";
import { Dimensions, FlatList, LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, Pressable, StyleSheet, View } from "react-native"; import { Dimensions, FlatList, LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, Pressable, StyleSheet, View } from "react-native";
@@ -18,7 +19,7 @@ export default function SeasonCarousel({
}) { }) {
const [currentIndex, setCurrentIndex] = React.useState(0); const [currentIndex, setCurrentIndex] = React.useState(0);
const [sliderWidth, setSliderWidth] = React.useState(Math.floor(WINDOW_WIDTH - 20)); const [sliderWidth, setSliderWidth] = React.useState(Math.floor(WINDOW_WIDTH - 20));
const listRef = React.useRef<FlatList<Season> | null>(null); const listRef = React.useRef<FlatList<Season>>(null);
const onLayout = (e: LayoutChangeEvent) => { const onLayout = (e: LayoutChangeEvent) => {
const w = Math.max(0, Math.floor(e.nativeEvent.layout.width)); const w = Math.max(0, Math.floor(e.nativeEvent.layout.width));
@@ -47,9 +48,9 @@ export default function SeasonCarousel({
return ( return (
<View style={{ marginBottom: 10, backgroundColor: "#1b1e2b", borderRadius: 10 }} onLayout={onLayout}> <View style={{ marginBottom: 10, backgroundColor: "#1b1e2b", borderRadius: 10 }} onLayout={onLayout}>
<FlatList <FlatList
ref={(r) => (listRef.current = r)} ref={listRef}
data={seasons} data={seasons}
keyExtractor={(season, idx) => `${show.showId}-${(season as any)?.seasonId ?? `season-${idx}`}`} keyExtractor={(season, idx) => `${show.id}-${(season as any)?.seasonId ?? `season-${idx}`}`}
horizontal horizontal
pagingEnabled pagingEnabled
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
@@ -71,7 +72,7 @@ export default function SeasonCarousel({
<View style={carouselStyles.dotsRow}> <View style={carouselStyles.dotsRow}>
{seasons.map((_, i) => ( {seasons.map((_, i) => (
<View key={`dot-${show.showId}-${i}`} style={[carouselStyles.dot, i === currentIndex && carouselStyles.dotActive]} /> <View key={`dot-${show.id}-${i}`} style={[carouselStyles.dot, i === currentIndex && carouselStyles.dotActive]} />
))} ))}
</View> </View>

View File

@@ -1,8 +1,13 @@
import { Season, Show } from "@/app/types"; import { Season } from "@/apis/seasonApi";
import GenreTag from "@/components/discovery/GenreTag"; import { Show } from "@/apis/showApi";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { router } from "expo-router";
import React from "react";
import { Image, StyleSheet, Text, TouchableOpacity, View } from "react-native"; import { Image, StyleSheet, Text, TouchableOpacity, View } from "react-native";
export default function ShowBox({ export default function ShowBox({
show, show,
displayedSeason, displayedSeason,
@@ -14,9 +19,14 @@ export default function ShowBox({
}) { }) {
const navigation = useNavigation(); const navigation = useNavigation();
const goToShow = React.useCallback((id: number) => {
router.push({ pathname: "/showDetails", params: { id: String(id) } });
}, []);
return ( return (
<TouchableOpacity <TouchableOpacity
onPress={() => navigation.navigate("ShowDetail", { show })} onPress={() => goToShow(Number(show.id))}
style={ style={
!shadow !shadow
? [styles.showContainer, { backgroundColor: "#1b1e2b", paddingBottom: 0 }] ? [styles.showContainer, { backgroundColor: "#1b1e2b", paddingBottom: 0 }]
@@ -24,7 +34,7 @@ export default function ShowBox({
} }
> >
<View style={styles.showImageContainer}> <View style={styles.showImageContainer}>
<Image source={{ uri: show.thumbnailUrl }} style={styles.showImage} /> <Image source={{ uri: show.thumbnailUri }} style={styles.showImage} />
{show.running && <Text style={styles.runningTag}>LIVE</Text>} {show.running && <Text style={styles.runningTag}>LIVE</Text>}
</View> </View>
@@ -33,19 +43,22 @@ export default function ShowBox({
{displayedSeason ? ( {displayedSeason ? (
<Text style={{ fontWeight: "bold", color: "#aac0ce" }}> <Text style={{ fontWeight: "bold", color: "#aac0ce" }}>
Staffel {displayedSeason.seasonNumber} ( Staffel {displayedSeason.seasonNumber}
{new Date(displayedSeason.startDate).getFullYear()}) {displayedSeason.startDate
? ` (${new Date(displayedSeason.startDate).getFullYear()})`
: ""}
</Text> </Text>
) : null} ) : null}
<Text style={styles.showDescription} numberOfLines={8} ellipsizeMode="tail"> <Text style={styles.showDescription} numberOfLines={8} ellipsizeMode="tail">
{show.description} {show.description}
</Text> </Text>
<View style={styles.showGenreTagContainer}> <View style={styles.showGenreTagContainer}>
{show.genre.split(", ").map((genre: any) => ( {/* {show.genres.map((genre: string) => (
<GenreTag key={genre}>{genre}</GenreTag> <GenreTag key={genre}>{genre}</GenreTag>
))} ))} */}
</View> </View>
</View> </View>
</TouchableOpacity> </TouchableOpacity>

View File

@@ -7,8 +7,9 @@ export const getIconName = (type: AutoCompleteItem["type"]) => {
case "SHOW": case "SHOW":
return "television"; return "television";
case "YEAR": case "YEAR":
case "CUSTOM":
return "calendar"; return "calendar";
case "CUSTOM":
return "tag";
default: default:
return "tag"; return "tag";
} }