update
This commit is contained in:
29
apis/searchApi.ts
Normal file
29
apis/searchApi.ts
Normal 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[];
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 },
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user