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:
+2
-1
@@ -9,4 +9,5 @@ prisma/migrations/*
|
|||||||
output.xml
|
output.xml
|
||||||
report.html
|
report.html
|
||||||
log.html
|
log.html
|
||||||
.expo
|
.expo
|
||||||
|
node_modules/
|
||||||
@@ -2,6 +2,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import AuthenticationView from './views/AuthenticationView';
|
import AuthenticationView from './views/AuthenticationView';
|
||||||
import HomeView from './views/HomeView';
|
import HomeView from './views/HomeView';
|
||||||
|
import SearchView from './views/SearchView';
|
||||||
import SetttingsNavigator from './views/SettingsView';
|
import SetttingsNavigator from './views/SettingsView';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import { useSelector } from './state/Store';
|
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="Home" component={HomeView} options={{ title: translate('welcome') }} />
|
||||||
<Stack.Screen name="Settings" component={SetttingsNavigator} options={{ title: 'Settings' }} />
|
<Stack.Screen name="Settings" component={SetttingsNavigator} options={{ title: 'Settings' }} />
|
||||||
<Stack.Screen name="Song" component={SongLobbyView} options={{ title: translate('play') }} />
|
<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>
|
export const publicRoutes = <React.Fragment>
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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}
|
shadow={3}
|
||||||
flexDirection='column'
|
flexDirection='column'
|
||||||
alignContent='space-around'
|
alignContent='space-around'
|
||||||
bg={(isHovered || isFocused) ? 'coolGray.200' : undefined }
|
bg={(isHovered || isFocused) ? 'coolGray.200' : 'background.50' }
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
style={{ zIndex: 0, aspectRatio: 1, margin: 5, borderRadius: CardBorderRadius}}
|
style={{ zIndex: 0, aspectRatio: 1, margin: 5, borderRadius: CardBorderRadius}}
|
||||||
|
|||||||
@@ -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;
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Chromacase",
|
|
||||||
"lockfileVersion": 2,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user