diff --git a/app.json b/app.json index dd1be3c..040edba 100644 --- a/app.json +++ b/app.json @@ -33,7 +33,7 @@ "image": "./assets/images/icon.png", "imageWidth": 300, "resizeMode": "contain", - "backgroundColor": "#000456" + "backgroundColor": "#000457" } ], "expo-web-browser" diff --git a/app/(tabs)/explore.tsx b/app/(tabs)/explore.tsx index 5b60f5a..2d5b0d8 100644 --- a/app/(tabs)/explore.tsx +++ b/app/(tabs)/explore.tsx @@ -1,5 +1,4 @@ import { AutoCompleteItem } from "@/apis/autoCompleteApi"; -import { SearchResultItem } from "@/apis/searchApi"; import { Season } from "@/apis/seasonApi"; import { Show } from "@/apis/showApi"; import styles from "@/app/tabStyles/indexStyles"; @@ -8,33 +7,52 @@ import { useDiscoveryContext } from "@/contexts/DiscoveryContext"; import { FontAwesome } from "@expo/vector-icons"; import Feather from "@expo/vector-icons/Feather"; import React from "react"; -import { Keyboard, ScrollView, Text, TextInput, TouchableOpacity, View } from "react-native"; +import { + Keyboard, + ScrollView, + Text, + TextInput, + TouchableOpacity, + TouchableWithoutFeedback, + View, +} from "react-native"; -import { useSearch } from "@/hooks/useSearch"; import { getShowById } from "@/apis/showApi"; import PersonRow from "@/components/discovery/PersonRow"; import SeasonCarousel from "@/components/discovery/SeasonCarousel"; import TagChip from "@/components/discovery/TagChip"; -import { getIconName, mapApiPersonToUI, mapApiSeasonToUI, mapApiShowToUI } from "@/utils/searchMapping"; +import { useSearch } from "@/hooks/useSearch"; +import { + getIconName, + mapApiPersonToUI, + mapApiSeasonToUI, + mapApiShowToUI, +} from "@/utils/searchMapping"; export default function ExploreScreen() { const { query, setQuery, suggestions } = useDiscoveryContext(); const [tags, setTags] = React.useState([]); const tagStrings = React.useMemo(() => tags.map((t) => t.text), [tags]); - const { data: results = [] } = useSearch(tagStrings); - // Show metadata cache by id (filled from SHOW results and lazy-loaded by id) + const { + data: results = [], + error, + refetch, + // isLoading, // optional, falls benötigt + } = useSearch(tagStrings as string[]); + + // Lokaler Show-Cache const [showsById, setShowsById] = React.useState>({}); - // --- helpers --- - + // Steuerung für Vorschlagsliste + const [showSuggestions, setShowSuggestions] = React.useState(false); function tagAdded(tag: AutoCompleteItem) { const nextTags = tags.some((t) => t.text === tag.text) ? tags : [...tags, tag]; setTags(nextTags); - setQuery(""); + setShowSuggestions(false); Keyboard.dismiss(); } @@ -43,7 +61,7 @@ export default function ExploreScreen() { setTags(nextTags); } - // Keep our local show cache in sync with SHOW items returned by search + // Cache mit SHOW-Resultaten füllen React.useEffect(() => { const fromResults: Record = {}; for (const r of results) { @@ -57,9 +75,7 @@ export default function ExploreScreen() { } }, [results]); - - - // Group SEASON results by showId + // SEASON-Ergebnisse nach showId gruppieren const seasonsByShowId = React.useMemo(() => { const map = new Map(); for (const r of results) { @@ -71,7 +87,6 @@ export default function ExploreScreen() { list.push(s as Season); map.set(key, list); } - // sort seasons per show by startDate asc for (const [k, list] of map) { list.sort((a, b) => { const da = a?.startDate ? new Date(a.startDate).getTime() : Number.POSITIVE_INFINITY; @@ -83,7 +98,7 @@ export default function ExploreScreen() { return map; }, [results]); - // Lazy fetch missing shows needed for Season carousels + // Fehlende Shows für Carousels nachladen React.useEffect(() => { const needed = Array.from(seasonsByShowId.keys()).filter((id) => !showsById[id]); if (needed.length === 0) return; @@ -97,7 +112,7 @@ export default function ExploreScreen() { const next: Record = {}; for (const s of fetched) { if (!s) continue; - next[s.id] = s; // wichtig: s.id, nicht s.showId + next[s.id] = s; } if (Object.keys(next).length) { setShowsById((prev) => ({ ...prev, ...next })); @@ -107,10 +122,12 @@ export default function ExploreScreen() { } })(); - return () => { cancelled = true; }; + return () => { + cancelled = true; + }; }, [seasonsByShowId, showsById]); - // PERSON hits shown at the top (like old screen) + // PERSON-Resultate const persons = React.useMemo(() => { return results .filter((r) => r.type === "PERSON") @@ -118,120 +135,309 @@ export default function ExploreScreen() { .sort((a, b) => (a.name || "").localeCompare(b.name || "")); }, [results]); + // Moderner Fehlerblock + if (error) { + return ( + + + ⚠️ + + Fehler beim Laden + + + {error?.message || "Ein unerwarteter Fehler ist aufgetreten."} + + + { + if (typeof refetch === "function") refetch(); + }} + style={{ + marginTop: 6, + backgroundColor: "rgba(255,255,255,0.15)", + paddingVertical: 10, + paddingHorizontal: 18, + borderRadius: 8, + }} + > + Erneut versuchen + + + + ); + } + + const noResults = persons.length === 0 && seasonsByShowId.size === 0; + return ( Durchsuchen - - {/* Search bar */} - - { - if (!query.trim()) return; - tagAdded({ type: "CUSTOM", text: query.trim() }); - }} - autoCapitalize="none" - /> + { + Keyboard.dismiss(); + setShowSuggestions(false); + }} + > + + + + { + setQuery(t); + if (t.length > 0 && !showSuggestions) setShowSuggestions(true); + if (t.length === 0) setShowSuggestions(false); + }} + placeholder="Wonach suchst du?" + placeholderTextColor="" + style={styles.searchInput} + returnKeyType="search" + onFocus={() => setShowSuggestions(true)} + onSubmitEditing={() => { + if (!query.trim()) return; + tagAdded({ type: "CUSTOM", text: query.trim() }); + }} + autoCapitalize="none" + /> - {query.length === 0 ? ( - - ) : ( - setQuery("")} /> - )} - + {query.length === 0 ? ( + + ) : ( + { + setQuery(""); + setShowSuggestions(false); + }} + /> + )} + - {/* Tag chips */} - - {tags.map((tag) => ( - { - tagRemoved(tag); - - }} - /> - ))} - - - {/* Suggestions dropdown */} - {query.length > 0 && ( - - Suchvorschläge - - {suggestions.map((suggestion, idx) => ( - tagAdded(suggestion)} - > - - {suggestion.text} - - ))} - - - )} - - - {/* Results */} - - - {/* Personen Section (top) */} - {persons.length > 0 && ( - - Personen - {persons.slice(0, 5).map((p) => ( - + + {tags.map((tag) => ( + { + tagRemoved(tag); + }} + /> ))} - )} - {/* Staffeln grouped by Show with page view */} - - Staffeln - - {Array.from(seasonsByShowId.entries()).map(([showId, seasons]) => { - const show = showsById[Number(showId)]; - if (!seasons || seasons.length === 0) return null; - // If show metadata is not yet loaded, render a minimal ShowBox fallback once per page item - if (!show) { - return ( - } - /> - ); - } - return ( - } - /> - ); - })} - - {seasonsByShowId.size === 0 && ( - - Keine Staffeln gefunden. Passe deine Tags an. - + {/* Suggestions dropdown */} + {query.length > 0 && showSuggestions && ( + + Suchvorschläge + + {suggestions.map((suggestion, idx) => ( + tagAdded(suggestion)} + > + + + {suggestion.text} + + + ))} + + )} - - + + {/* Results */} + + + {/* Personen Section (top) */} + {persons.length > 0 && ( + + Personen + {persons.slice(0, 5).map((p) => ( + + ))} + + )} + + + {seasonsByShowId.size > 0 && ( + Staffeln + )} + + {Array.from(seasonsByShowId.entries()).map(([showId, seasons]) => { + const show = showsById[Number(showId)]; + if (!seasons || seasons.length === 0) return null; + + if (!show) { + return ( + ( + + )} + /> + ); + } + return ( + ( + + )} + /> + ); + })} + + {/* Schöner Empty-State */} + {noResults && ( + + + + Keine Ergebnisse gefunden + + + Passen Sie Ihre Tags an oder setzen Sie die Filter zurück. + + + + { + setTags([]); + setQuery(""); + setShowSuggestions(false); + Keyboard.dismiss(); + if (typeof refetch === "function") refetch(); + }} + style={{ + backgroundColor: "rgba(255,255,255,0.15)", + paddingVertical: 10, + paddingHorizontal: 14, + borderRadius: 8, + }} + > + + Filter zurücksetzen + + + + { + setQuery(""); + setShowSuggestions(false); + Keyboard.dismiss(); + }} + style={{ + backgroundColor: "rgba(255,255,255,0.08)", + paddingVertical: 10, + paddingHorizontal: 14, + borderRadius: 8, + }} + > + Eingabe löschen + + + + )} + + + + + ); } diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 452ba3d..509b8d1 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -2,6 +2,7 @@ import styles from "@/app/tabStyles/indexStyles"; import ShowCard from "@/components/ui/ShowCard"; import { useShows } from "@/hooks/useShows"; import { useStreamingServices } from "@/hooks/useStreamingServices"; +import Feather from "@expo/vector-icons/Feather"; import * as Haptics from "expo-haptics"; import { router } from "expo-router"; import React from "react"; @@ -89,6 +90,28 @@ export default function HomeScreen() { + { + router.push("/legal"); + }} + style={{ + position: "absolute", + left: 16, + top: "63%", + transform: [{ translateY: -12 }], + height: 40, + width: 40, + alignItems: "center", + justifyContent: "center", + borderRadius: 10, + backgroundColor: "rgba(255,255,255,0.06)", + }} + accessibilityRole="button" + accessibilityLabel="Menü öffnen" + > + + + FLTR { const streamingService = streamingServices[ - `assets.images.streamingServices.${serviceName.toLowerCase()}` + `assets.images.streamingServices.${serviceName.toLowerCase()}` ]; return ( streamingServices[ - `assets.images.streamingServices.${s.toLowerCase()}` + `assets.images.streamingServices.${s.toLowerCase()}` ] )} genres={show.genres} {...(showLiveBadge ? { - liveBadgeText: "LIVE", - liveBadgeContainerStyle: styles.liveBadgeContainer, - } + liveBadgeText: "LIVE", + liveBadgeContainerStyle: styles.liveBadgeContainer, + } : {})} /> ); diff --git a/app/_layout.tsx b/app/_layout.tsx index 3a33173..7117d06 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -23,7 +23,14 @@ export default function RootLayout() { headerShown: false, }} /> + + ); diff --git a/app/legal.tsx b/app/legal.tsx new file mode 100644 index 0000000..9bdbae3 --- /dev/null +++ b/app/legal.tsx @@ -0,0 +1,154 @@ +import Feather from "@expo/vector-icons/Feather"; +import { router } from "expo-router"; +import React from "react"; +import { ScrollView, Text, TouchableOpacity, View } from "react-native"; + +export default function LegalScreen() { + return ( + + + + + + Info + + + router.back()} + accessibilityRole="button" + accessibilityLabel="Modal schließen" + style={{ + height: 40, + width: 40, + alignItems: "center", + justifyContent: "center", + borderRadius: 10, + backgroundColor: "rgba(255,255,255,0.08)", + }} + > + + + + + + {/* Impressum Card */} + + + Impressum + + + + Berg Autosoft + Joe Felipe Berg + Stöckener Straße 35 + 30419 Hannover + + + + + + +49 1522 5642948 + kontakt@berg-autosoft.de + + + + + + Steuernummer: 25/103/17193 + USt-ID: DE361689728 + + + + {/* Support Card */} + + + Support + + + + Sollten Sie Probleme bei der Nutzung der iOS- oder Android-App FLTR + haben, wenden Sie sich bitte direkt an den Support. + + + + + Schreiben Sie eine E-Mail an: + + developer@berg-autosoft.de + + + + Wir bemühen uns, Ihr Anliegen so schnell wie möglich zu bearbeiten. + + + + {/* Footer */} + + + © 2025 Berg Autosoft + + + + + ); +} + +const styles = { + mono: { + color: "rgba(255,255,255,0.92)", + fontSize: 16, + } as const, + dim: { + color: "rgba(255,255,255,0.75)", + fontSize: 14, + } as const, + body: { + color: "rgba(255,255,255,0.88)", + fontSize: 14, + lineHeight: 20, + } as const, +}; diff --git a/assets/images/adaptive-icon.png b/assets/images/adaptive-icon.png deleted file mode 100644 index 03d6f6b..0000000 Binary files a/assets/images/adaptive-icon.png and /dev/null differ diff --git a/assets/images/favicon.png b/assets/images/favicon.png deleted file mode 100644 index e75f697..0000000 Binary files a/assets/images/favicon.png and /dev/null differ diff --git a/assets/images/fltr.png b/assets/images/fltr.png deleted file mode 100644 index 2520853..0000000 Binary files a/assets/images/fltr.png and /dev/null differ diff --git a/assets/images/partial-react-logo.png b/assets/images/partial-react-logo.png deleted file mode 100644 index 66fd957..0000000 Binary files a/assets/images/partial-react-logo.png and /dev/null differ diff --git a/assets/images/react-logo.png b/assets/images/react-logo.png deleted file mode 100644 index 9d72a9f..0000000 Binary files a/assets/images/react-logo.png and /dev/null differ diff --git a/assets/images/react-logo@2x.png b/assets/images/react-logo@2x.png deleted file mode 100644 index 2229b13..0000000 Binary files a/assets/images/react-logo@2x.png and /dev/null differ diff --git a/assets/images/react-logo@3x.png b/assets/images/react-logo@3x.png deleted file mode 100644 index a99b203..0000000 Binary files a/assets/images/react-logo@3x.png and /dev/null differ diff --git a/assets/images/splash-icon.png b/assets/images/splash-icon.png deleted file mode 100644 index 03d6f6b..0000000 Binary files a/assets/images/splash-icon.png and /dev/null differ