This commit is contained in:
DevOFVictory
2025-10-08 17:26:35 +02:00
parent a01ffcd2bb
commit 21d2cb158e
3 changed files with 222 additions and 90 deletions

29
apis/searchApi.ts Normal file
View File

@@ -0,0 +1,29 @@
export type SearchItemType = "SHOW" | "PERSON" | "SEASON" | string;
export type SearchResultItem = {
type: SearchItemType;
data: any;
};
const DISCOVER_BASE = "http://45.157.177.99:8080/discover/search";
export async function getSearchResults(
tags: string[] | string,
limit = 10,
signal?: AbortSignal
): Promise<SearchResultItem[]> {
const tagList = Array.isArray(tags) ? tags : [tags];
const filteredTags = tagList.map((t) => t.trim()).filter(Boolean);
if (!filteredTags.length) return [];
const url = `${DISCOVER_BASE}?tags=${encodeURIComponent(
filteredTags.join(",")
)}&limit=${limit}`;
const res = await fetch(url, { signal });
if (!res.ok) throw new Error("AutoComplete failed " + res.status);
const data: unknown = await res.json();
if (!Array.isArray(data)) return [];
return data as SearchResultItem[];
}

View File

@@ -1,41 +1,70 @@
import { AutoCompleteItem } from "@/apis/autoCompleteApi";
import { getSearchResults, SearchResultItem } from "@/apis/searchApi";
import styles from "@/app/tabStyles/indexStyles"; import styles from "@/app/tabStyles/indexStyles";
import { useDiscoveryContext } from "@/contexts/DiscoveryContext";
import { FontAwesome } from "@expo/vector-icons";
import Feather from "@expo/vector-icons/Feather";
import React from "react"; import React from "react";
import { import {
Text,
View,
TextInput,
FlatList,
TouchableOpacity,
Keyboard, Keyboard,
TouchableWithoutFeedback, ScrollView,
Text,
TextInput,
TouchableOpacity,
View
} from "react-native"; } from "react-native";
import Feather from "@expo/vector-icons/Feather";
import { useDiscoveryContext } from "@/contexts/DiscoveryContext";
import { useShowContext } from "@/contexts/ShowContext";
export default function TabTwoScreen() { export default function TabTwoScreen() {
const { query, setQuery, suggestions, loading, error, clear } = const { query, setQuery, suggestions } = useDiscoveryContext();
useDiscoveryContext();
const { shows } = useShowContext(); const [tags, setTags] = React.useState<AutoCompleteItem[]>([]);
const [searchResults, setSearchResults] = React.useState<SearchResultItem[]>([]);
const getIconName = (type: AutoCompleteItem["type"]) => {
switch (type) {
case "PERSON":
return "user";
case "SHOW":
return "television";
case "YEAR":
return "calendar";
default:
return "tag";
}
};
function tagAdded(tag: AutoCompleteItem) {
console.log("Tag added:", tag);
const nextTags = tags.some((t) => t.text === tag.text)
? tags
: [...tags, tag];
setTags(nextTags);
const tagStrings = nextTags.map((t) => t.text);
getSearchResults(tagStrings, 20)
.then(setSearchResults)
.catch(console.error);
setQuery("");
Keyboard.dismiss();
console.log("Searching with tags:", tagStrings);
}
const personSuggestions = React.useMemo(
() => suggestions.filter((s) => s.type === "PERSON"),
[suggestions]
);
const showSuggestions = React.useMemo(
() => suggestions.filter((s) => s.type === "SHOW"),
[suggestions]
);
const [tag, setTag] = React.useState<string | null>(null);
return ( return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss}> <View style={[styles.mainContainer]}>
<View style={styles.mainContainer}> <View style={styles.header}>
<View style={styles.header}> <Text style={[styles.title, { fontSize: 28 }]}>Durchsuchen</Text>
<Text style={[styles.title, { fontSize: 28 }]}>Durchsuchen</Text> </View>
</View>
<View style={{ paddingHorizontal: 10 }}>
<View style={styles.searchContainer}> <View style={styles.searchContainer}>
<TextInput <TextInput
@@ -47,11 +76,11 @@ export default function TabTwoScreen() {
fontSize: 18, fontSize: 18,
fontWeight: "500", fontWeight: "500",
color: "hsl(221, 39%, 80%)", color: "hsl(221, 39%, 80%)",
width: "90%",
height: "100%",
}} }}
returnKeyType="search" returnKeyType="search"
onSubmitEditing={() => { onSubmitEditing={() => console.log("Search:", query)}
console.log("Search:", query);
}}
autoCapitalize="none" autoCapitalize="none"
/> />
@@ -67,52 +96,101 @@ export default function TabTwoScreen() {
)} )}
</View> </View>
{tag && ( <View style={styles.tagContainer}>
<View style={styles.tagContainer}> {tags.map((tag) => (
<Text style={styles.tagLabel}>{tag}</Text> <TouchableOpacity
<Feather key={tag.text}
name="x" onPress={() =>
size={18} setTags((prev) => prev.filter((t) => t.text !== tag.text))
color="hsl(221, 39%, 80%)" }
onPress={() => setTag(null)} >
style={{ marginLeft: "auto", marginRight: 10 }} <View style={styles.tag}>
/> <FontAwesome
</View> name={getIconName(tag.type)}
)} size={16}
color="#bbb"
style={{ marginRight: 6 }}
/>
<Text style={styles.tagLabel}>{tag.text}</Text>
<FontAwesome
name="times-circle"
size={16}
color="#bbb"
style={{ marginLeft: 6 }}
/>
</View>
</TouchableOpacity>
))}
</View>
{query.length > 0 && ( {query.length > 0 && (
<> <View style={styles.suggestionsSection}>
<View style={styles.suggestionsSection}> <Text style={styles.suggestionTitle}>Suchvorschläge</Text>
<Text style={styles.suggestionTitle}>Suchvorschläge</Text> <ScrollView keyboardShouldPersistTaps="handled">
{suggestions.map((suggestion, idx) => (
<TouchableOpacity
key={suggestion.text + "_" + idx}
style={styles.suggestionContainer}
onPress={() => {
tagAdded(suggestion);
}}
>
<FontAwesome
name={getIconName(suggestion.type)}
size={16}
color="hsl(0, 0%, 90%)"
/>
<Text style={styles.suggestionLabel}>{suggestion.text}</Text>
</TouchableOpacity>
))}
{showSuggestions.map((suggestion, idx) => ( </ScrollView>
<TouchableOpacity
key={suggestion.text + "_" + idx}
style={styles.suggestionContainer} </View>
onPress={() => {
setTag(suggestion.text);
}}
>
<View style={styles.imageContainer}></View>
<Text style={styles.suggestionLabel}>{suggestion.text}</Text>
</TouchableOpacity>
))}
{personSuggestions.map((suggestion, idx) => (
<TouchableOpacity
key={suggestion.text + "_" + idx}
style={styles.suggestionContainer}
onPress={() => {
setTag(suggestion.text);
}}
>
<View style={styles.imageContainer}></View>
<Text style={styles.suggestionLabel}>{suggestion.text}</Text>
</TouchableOpacity>
))}
</View>
</>
)} )}
</View> </View>
</TouchableWithoutFeedback>
<View style={{ flex: 1 }} >
<ScrollView keyboardShouldPersistTaps="handled">
{searchResults.map((result: SearchResultItem, idx) => {
switch (result.type) {
case "PERSON":
return (
<View key={result.data.id + "_" + idx} style={styles.personRow}>
<View style={styles.avatarCircle}>
<FontAwesome name="user" size={22} color="#ccc" />
</View>
{/* Text */}
<View style={{ flex: 1 }}>
<Text style={styles.personName}>{result.data.name || "Unbekannt"} ({"25"})</Text>
<Text style={styles.personMeta}>
aus: {"unterscheidlichen Shows"}
</Text>
</View>
{/* Chevron */}
<FontAwesome name="chevron-right" size={14} color="#888" />
</View>
);
case "SHOW":
return (
<View key={result.data.id + "_" + idx}>
<Text style={{ color: "skyblue" }}>
{result.data.title} (Show)
</Text>
</View>
);
default:
return null;
}
})}
</ScrollView>
</View>
</View>
); );
} }

View File

@@ -4,6 +4,7 @@ export default StyleSheet.create({
mainContainer: { mainContainer: {
flex: 1, flex: 1,
backgroundColor: "hsl(221, 39%, 11%)", backgroundColor: "hsl(221, 39%, 11%)",
// paddingHorizontal: 10,
}, },
header: { header: {
height: 125, height: 125,
@@ -43,7 +44,7 @@ export default StyleSheet.create({
marginTop: 20, marginTop: 20,
}, },
searchContainer: { searchContainer: {
width: "90%", width: "100%",
height: 60, height: 60,
marginHorizontal: "auto", marginHorizontal: "auto",
backgroundColor: "hsl(221, 39%, 8%)", backgroundColor: "hsl(221, 39%, 8%)",
@@ -71,7 +72,10 @@ export default StyleSheet.create({
fontWeight: "500", fontWeight: "500",
}, },
suggestionsSection: { suggestionsSection: {
width: "90%", position: "absolute",
zIndex: 10,
top: 70,
width: "100%",
height: "auto", height: "auto",
paddingBottom: 15, paddingBottom: 15,
borderRadius: 20, borderRadius: 20,
@@ -89,6 +93,7 @@ export default StyleSheet.create({
shadowOpacity: 0.25, shadowOpacity: 0.25,
shadowRadius: 3.84, shadowRadius: 3.84,
elevation: 5, elevation: 5,
// opacity: 0.9,
}, },
suggestionTitle: { suggestionTitle: {
color: "hsl(0, 0%, 60%)", color: "hsl(0, 0%, 60%)",
@@ -119,26 +124,46 @@ export default StyleSheet.create({
fontWeight: "500", fontWeight: "500",
marginLeft: 10, marginLeft: 10,
}, },
tagContainer: { tag: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
backgroundColor: "hsl(221, 39%, 8%)", backgroundColor: "#333",
borderWidth: 1.5, borderRadius: 999,
borderColor: "hsl(221, 39%, 15%)", paddingHorizontal: 10,
height: "auto", paddingVertical: 6,
width: "auto", marginRight: 8,
paddingVertical: 10, marginBottom: 8,
borderRadius: 50, marginTop: 5,
marginTop: 15,
marginLeft: 20,
justifyContent: "space-between",
}, },
tagLabel: { tagLabel: {
color: "hsl(0, 0%, 90%)", color: "white",
fontSize: 14, marginRight: 5
fontWeight: "500",
marginLeft: 15,
textAlign: "center",
}, },
tagContainer: {
width: "100%",
flexDirection: "row",
flexWrap: "wrap",
marginTop: 10,
},
personRow: {
width: "100%",
flexDirection: "row",
alignItems: "center",
backgroundColor: "#1b1e2b",
borderRadius: 10,
paddingHorizontal: 10,
paddingVertical: 10,
marginBottom: 8,
},
avatarCircle: {
width: 40,
height: 40,
borderRadius: 999,
backgroundColor: "#2a2f45",
alignItems: "center",
justifyContent: "center",
marginRight: 10,
},
personName: { color: "white", fontSize: 16, fontWeight: "600" },
personMeta: { color: "#bbb", fontSize: 12, marginTop: 2 },
}); });