Feature/addsearchpage (#85)
* Added a searchbar view with a debounce * Added SearchBar with suggestions handled * Front: Fix style of search suggestions * added multiple types of suggestions and fixed image rendered * Search bar revamped done * removed unused files Co-authored-by: Arthi-chaud <arthur.jamet@gmail.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,4 +9,5 @@ prisma/migrations/*
|
||||
output.xml
|
||||
report.html
|
||||
log.html
|
||||
.expo
|
||||
.expo
|
||||
node_modules/
|
||||
@@ -2,6 +2,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import React from 'react';
|
||||
import AuthenticationView from './views/AuthenticationView';
|
||||
import HomeView from './views/HomeView';
|
||||
import SearchView from './views/SearchView';
|
||||
import SetttingsNavigator from './views/SettingsView';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { useSelector } from './state/Store';
|
||||
@@ -14,6 +15,7 @@ export const protectedRoutes = <>
|
||||
<Stack.Screen name="Home" component={HomeView} options={{ title: translate('welcome') }} />
|
||||
<Stack.Screen name="Settings" component={SetttingsNavigator} options={{ title: 'Settings' }} />
|
||||
<Stack.Screen name="Song" component={SongLobbyView} options={{ title: translate('play') }} />
|
||||
<Stack.Screen name="Search" component={SearchView} options={{ title: translate('search') }} />
|
||||
</>;
|
||||
|
||||
export const publicRoutes = <React.Fragment>
|
||||
|
||||
150
front/components/SearchBar.tsx
Normal file
150
front/components/SearchBar.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import {
|
||||
Input,
|
||||
Column,
|
||||
Row,
|
||||
Text,
|
||||
Pressable,
|
||||
HStack,
|
||||
VStack,
|
||||
Image,
|
||||
Icon,
|
||||
Square,
|
||||
} from "native-base";
|
||||
import React from "react";
|
||||
import { Ionicons, FontAwesome } from "@expo/vector-icons";
|
||||
|
||||
export enum SuggestionType {
|
||||
TEXT,
|
||||
ILLUSTRATED,
|
||||
}
|
||||
|
||||
export type SuggestionList = {
|
||||
type: SuggestionType;
|
||||
data: SuggestionProps | IllustratedSuggestionProps;
|
||||
}[];
|
||||
|
||||
export interface SearchBarProps {
|
||||
onTextChange: (text: string) => void;
|
||||
onTextSubmit: (text: string) => void;
|
||||
suggestions: SuggestionList;
|
||||
}
|
||||
export interface IllustratedSuggestionProps {
|
||||
text: string;
|
||||
subtext: string;
|
||||
imageSrc: string;
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
export interface SuggestionProps {
|
||||
text: string;
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
// debounce function
|
||||
const debounce = (func: any, delay: number) => {
|
||||
let inDebounce: any;
|
||||
return function (this: any) {
|
||||
const context = this;
|
||||
const args = arguments;
|
||||
clearTimeout(inDebounce);
|
||||
inDebounce = setTimeout(() => func.apply(context, args), delay);
|
||||
};
|
||||
};
|
||||
|
||||
const IllustratedSuggestion = ({
|
||||
text,
|
||||
subtext,
|
||||
imageSrc,
|
||||
onPress,
|
||||
}: IllustratedSuggestionProps) => {
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
margin={2}
|
||||
padding={2}
|
||||
bg={"white"}
|
||||
_hover={{
|
||||
bg: "primary.200",
|
||||
}}
|
||||
_pressed={{
|
||||
bg: "primary.300",
|
||||
}}
|
||||
>
|
||||
<HStack alignItems="center" space={4}>
|
||||
<Square size={"sm"}>
|
||||
<Image
|
||||
source={{ uri: imageSrc }}
|
||||
alt="Alternate Text"
|
||||
size="xs"
|
||||
rounded="lg"
|
||||
/>
|
||||
</Square>
|
||||
<VStack alignItems="flex-start">
|
||||
<Text fontSize="md">{text}</Text>
|
||||
<Text fontSize="sm" color="gray.500">
|
||||
{subtext}
|
||||
</Text>
|
||||
</VStack>
|
||||
</HStack>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
const TextSuggestion = ({ text, onPress }: SuggestionProps) => {
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
margin={2}
|
||||
padding={2}
|
||||
bg={"white"}
|
||||
_hover={{
|
||||
bg: "primary.200",
|
||||
}}
|
||||
_pressed={{
|
||||
bg: "primary.300",
|
||||
}}
|
||||
>
|
||||
<Row alignItems="center" space={4}>
|
||||
<Square size={"sm"}>
|
||||
<Icon size={"md"} as={Ionicons} name="search" />
|
||||
</Square>
|
||||
<Text fontSize="md">{text}</Text>
|
||||
</Row>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
// render the suggestions based on the type
|
||||
const SuggestionRenderer = (suggestions: SuggestionList) => {
|
||||
const suggestionRenderers = {
|
||||
[SuggestionType.TEXT]: TextSuggestion,
|
||||
[SuggestionType.ILLUSTRATED]: IllustratedSuggestion,
|
||||
};
|
||||
return suggestions.map((suggestion, index) => {
|
||||
const SuggestionComponent = suggestionRenderers[suggestion.type];
|
||||
return <SuggestionComponent {...suggestion.data} key={index} />;
|
||||
});
|
||||
};
|
||||
|
||||
const SearchBar = ({
|
||||
onTextChange,
|
||||
onTextSubmit,
|
||||
suggestions,
|
||||
}: SearchBarProps) => {
|
||||
const debouncedOnTextChange = React.useRef(
|
||||
debounce((t: string) => onTextChange(t), 70)
|
||||
).current;
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Search"
|
||||
type="text"
|
||||
onChangeText={debouncedOnTextChange}
|
||||
onSubmitEditing={(event) => onTextSubmit(event.nativeEvent.text)}
|
||||
/>
|
||||
<Column>{SuggestionRenderer(suggestions)}</Column>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchBar;
|
||||
41
front/components/SearchBarSuggestions.tsx
Normal file
41
front/components/SearchBarSuggestions.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from "react";
|
||||
import SearchBar from "../components/SearchBar";
|
||||
import { SuggestionList, SuggestionType } from "../components/SearchBar";
|
||||
interface SearchBarSuggestionsProps {
|
||||
onTextSubmit: (text: string) => void;
|
||||
suggestions: SuggestionList;
|
||||
}
|
||||
|
||||
// do a function that takes in a string and returns a list of filtered suggestions
|
||||
const filterSuggestions = (text: string, suggestions: SuggestionList) => {
|
||||
return suggestions.filter((suggestion) => {
|
||||
switch (suggestion.type) {
|
||||
case SuggestionType.TEXT:
|
||||
return suggestion.data.text.toLowerCase().includes(text.toLowerCase());
|
||||
case SuggestionType.ILLUSTRATED:
|
||||
return (
|
||||
suggestion.data.text.toLowerCase().includes(text.toLowerCase()) ||
|
||||
suggestion.data.subtext.toLowerCase().includes(text.toLowerCase())
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const SearchBarSuggestions = ({
|
||||
onTextSubmit,
|
||||
suggestions,
|
||||
}: SearchBarSuggestionsProps) => {
|
||||
const [searchText, setSearchText] = React.useState("");
|
||||
|
||||
return (
|
||||
<SearchBar
|
||||
onTextChange={(t) => setSearchText(t)}
|
||||
onTextSubmit={onTextSubmit}
|
||||
suggestions={
|
||||
searchText === "" ? [] : filterSuggestions(searchText, suggestions)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchBarSuggestions;
|
||||
@@ -18,7 +18,7 @@ const SongCard = (props: SongCardProps) => {
|
||||
shadow={3}
|
||||
flexDirection='column'
|
||||
alignContent='space-around'
|
||||
bg={(isHovered || isFocused) ? 'coolGray.200' : undefined }
|
||||
bg={(isHovered || isFocused) ? 'coolGray.200' : 'background.50' }
|
||||
>
|
||||
<Image
|
||||
style={{ zIndex: 0, aspectRatio: 1, margin: 5, borderRadius: CardBorderRadius}}
|
||||
|
||||
66
front/views/SearchView.tsx
Normal file
66
front/views/SearchView.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
import { useDispatch } from "../state/Store";
|
||||
import { translate } from "../i18n/i18n";
|
||||
import { Box, Button } from "native-base";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import SearchBarSuggestions from "../components/SearchBarSuggestions";
|
||||
import {
|
||||
SuggestionList,
|
||||
SuggestionType,
|
||||
IllustratedSuggestionProps,
|
||||
} from "../components/SearchBar";
|
||||
|
||||
const onTextSubmit = (text: string) => {
|
||||
console.log(text);
|
||||
};
|
||||
|
||||
const SearchView = () => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const IllustratedSuggestion: IllustratedSuggestionProps = {
|
||||
text: "Love Story",
|
||||
subtext: "Taylor Swift",
|
||||
imageSrc:
|
||||
"https://i.discogs.com/yHqu3pnLgJq-cVpYNVYu6mE-fbzIrmIRxc6vES5Oi48/rs:fit/g:sm/q:90/h:556/w:600/czM6Ly9kaXNjb2dz/LWRhdGFiYXNlLWlt/YWdlcy9SLTE2NjQ2/ODUwLTE2MDkwNDU5/NzQtNTkxOS5qcGVn.jpeg",
|
||||
onPress: () => navigation.navigate("Song", { songId: 1 }),
|
||||
};
|
||||
// fill the suggestions with the data from the backend
|
||||
const suggestions: SuggestionList = [
|
||||
{
|
||||
type: SuggestionType.ILLUSTRATED,
|
||||
data: IllustratedSuggestion,
|
||||
},
|
||||
{
|
||||
type: SuggestionType.ILLUSTRATED,
|
||||
data: IllustratedSuggestion,
|
||||
},
|
||||
{
|
||||
type: SuggestionType.ILLUSTRATED,
|
||||
data: {
|
||||
text: "Shed a Light",
|
||||
subtext: "Robin Schulz & David Guetta",
|
||||
imageSrc:
|
||||
"https://imgs.search.brave.com/O9j2Z-oWiniq3lj7d-dAOgXLWCIqnHaFegmaSeIkWOY/rs:fit:560:320:1/g:ce/aHR0cHM6Ly91cGxv/YWQud2lraW1lZGlh/Lm9yZy93aWtpcGVk/aWEvZW4vdGh1bWIv/OC84ZS9TaGVkX2Ff/TGlnaHRfUm9iaW5f/U2NodWx6LmpwZy81/MTJweC1TaGVkX2Ff/TGlnaHRfUm9iaW5f/U2NodWx6LmpwZw",
|
||||
onPress: () => navigation.navigate("Song", { songId: 1 }),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: SuggestionType.TEXT,
|
||||
data: {
|
||||
text: "Lady Gaga",
|
||||
onPress: () => navigation.navigate("Song", { songId: 1 }),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box style={{ padding: 10 }}>
|
||||
<SearchBarSuggestions
|
||||
onTextSubmit={onTextSubmit}
|
||||
suggestions={suggestions}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchView;
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "Chromacase",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
Reference in New Issue
Block a user