Merge pull request #336 from Chroma-Case/feat/adc/search-view-v2

implémentation de la search bar V2
This commit is contained in:
Clément Le Bihan
2023-12-07 16:55:19 +01:00
committed by GitHub
10 changed files with 262 additions and 53 deletions

View File

@@ -314,16 +314,10 @@ export class AuthController {
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ description: 'Successfully added score'})
@ApiUnauthorizedResponse({ description: 'Invalid token' })
@Patch('me/score/:score')
addScore(
@Request() req: any,
@Param('id') score: number,
) {
return this.usersService.addScore(
+req.user.id,
score,
);
@ApiOkResponse({ description: "Successfully added score" })
@ApiUnauthorizedResponse({ description: "Invalid token" })
@Patch("me/score/:score")
addScore(@Request() req: any, @Param("id") score: number) {
return this.usersService.addScore(+req.user.id, score);
}
}

View File

@@ -1,19 +1,16 @@
import {
Controller,
Get,
} from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { ScoresService } from './scores.service';
import { User } from '@prisma/client';
import { Controller, Get } from "@nestjs/common";
import { ApiOkResponse, ApiTags } from "@nestjs/swagger";
import { ScoresService } from "./scores.service";
import { User } from "@prisma/client";
@ApiTags('scores')
@Controller('scores')
@ApiTags("scores")
@Controller("scores")
export class ScoresController {
constructor(private readonly scoresService: ScoresService) {}
@ApiOkResponse({ description: 'Successfully sent the Top 20 players'})
@Get('top/20')
@ApiOkResponse({ description: "Successfully sent the Top 20 players" })
@Get("top/20")
getTopTwenty(): Promise<User[]> {
return this.scoresService.topTwenty();
}
}
}

View File

@@ -400,6 +400,19 @@ export default class API {
};
}
public static getAllGenres(): Query<Genre[]> {
return {
key: ['genres'],
exec: () =>
API.fetch(
{
route: '/genre',
},
{ handler: PlageHandler(GenreHandler) }
).then(({ data }) => data),
};
}
/**
* Retrive a song's musicXML partition
* @param songId the id to find the song
@@ -433,6 +446,19 @@ export default class API {
};
}
public static getAllArtists(): Query<Artist[]> {
return {
key: ['artists'],
exec: () =>
API.fetch(
{
route: `/artist`,
},
{ handler: PlageHandler(ArtistHandler) }
).then(({ data }) => data),
};
}
/**
* Retrive a song's chapters
* @param songId the id to find the song
@@ -716,24 +742,6 @@ export default class API {
};
}
public static getSongCursorInfos(songId: number): Query<SongCursorInfos> {
return {
key: ['cursorInfos', songId],
exec: () => {
return API.fetch(
{
route: `/song/${songId}/assets/cursors`,
},
{ handler: SongCursorInfosHandler }
);
},
};
}
public static getPartitionSvgUrl(songId: number): string {
return `${API.baseUrl}/song/${songId}/assets/partition`;
}
public static async updateUserTotalScore(score: number): Promise<void> {
await API.fetch({
route: `/auth/me/score/${score}`,
@@ -754,4 +762,21 @@ export default class API {
),
};
}
public static getSongCursorInfos(songId: number): Query<SongCursorInfos> {
return {
key: ['cursorInfos', songId],
exec: () => {
return API.fetch(
{
route: `/song/${songId}/assets/cursors`,
},
{ handler: SongCursorInfosHandler }
);
},
};
}
public static getPartitionSvgUrl(songId: number): string {
return `${API.baseUrl}/song/${songId}/assets/partition`;
}
}

View File

@@ -10,7 +10,7 @@ import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/
import { RootState, useSelector } from './state/Store';
import { useDispatch } from 'react-redux';
import { Translate, translate } from './i18n/i18n';
import SearchView from './views/SearchView';
import SearchView from './views/V2/SearchView';
import SettingsTab from './views/settings/SettingsView';
import { useQuery } from './Queries';
import API, { APIError } from './API';
@@ -49,11 +49,6 @@ const protectedRoutes = () =>
options: { headerShown: false },
link: '/music',
},
HomeNew: {
component: DiscoveryView,
options: { headerShown: false },
link: '/V2',
},
Play: {
component: PlayView,
options: { headerShown: false, title: translate('play') },

View File

@@ -108,7 +108,7 @@ const ScoreModal = (props: ScoreModalProps) => {
onPress={() =>
navigation.canGoBack()
? navigation.goBack()
: navigation.navigate('HomeNew', {})
: navigation.navigate('Home', {})
}
/>
</Row>

View File

@@ -10,7 +10,7 @@ import ScaffoldMobileCC from './ScaffoldMobileCC';
import { useAssets } from 'expo-asset';
const menu = [
{ type: 'main', title: 'menuDiscovery', icon: Discover, link: 'HomeNew' },
{ type: 'main', title: 'menuDiscovery', icon: Discover, link: 'Home' },
{ type: 'main', title: 'menuProfile', icon: User, link: 'User' },
{ type: 'main', title: 'menuMusic', icon: Music, link: 'Music' },
{ type: 'main', title: 'menuSearch', icon: SearchNormal1, link: 'Search' },

View File

@@ -0,0 +1,179 @@
import React from 'react';
import { Button, Text, Select, useBreakpointValue } from 'native-base';
import { ScrollView, View } from 'react-native';
import { Input } from 'native-base';
import ButtonBase from '../UI/ButtonBase';
import { AddSquare, CloseCircle, SearchNormal1 } from 'iconsax-react-native';
import { useQuery } from '../../Queries';
import API from '../../API';
import Genre from '../../models/Genre';
import { translate } from '../../i18n/i18n';
type ArtistChipProps = {
name: string;
selected?: boolean;
onPress: () => void;
};
const ArtistChipComponent = (props: ArtistChipProps) => {
return (
<View>
<Button variant={'ghost'} onPress={props.onPress}>
<View
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 10,
}}
>
{props.selected ? (
<CloseCircle size="32" color={'#ED4A51'} />
) : (
<AddSquare size="32" color={'#6075F9'} />
)}
<Text>{props.name}</Text>
</View>
</Button>
</View>
);
};
const SearchBarComponent = () => {
const [query, setQuery] = React.useState('');
const [genre, setGenre] = React.useState({} as Genre | undefined);
const [artist, setArtist] = React.useState('');
const artistsQuery = useQuery(API.getAllArtists());
const genresQuery = useQuery(API.getAllGenres());
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
const isMobileView = screenSize == 'small';
return (
<View>
<View
style={{
borderBottomWidth: 1,
borderBottomColor: '#9E9E9E',
display: 'flex',
flexDirection: isMobileView ? 'column' : 'row',
alignItems: 'center',
width: '100%',
margin: 5,
padding: 16,
gap: 10,
}}
>
<View
style={{
flexGrow: 0,
flexShrink: 0,
flexDirection: 'row',
flexWrap: 'wrap',
}}
>
{artist && (
<ArtistChipComponent
onPress={() => setArtist('')}
name={artist}
selected={true}
/>
)}
</View>
<View
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
flexGrow: 1,
width: '100%',
}}
>
<View
style={{
flexGrow: 1,
flexShrink: 1,
}}
>
<Input
type="text"
value={query}
variant={'unstyled'}
placeholder={translate('searchBarPlaceholder')}
style={{ width: '100%', height: 30 }}
onChangeText={(value) => setQuery(value)}
/>
</View>
<ButtonBase
type="menu"
icon={SearchNormal1}
style={{
flexShrink: 0,
flexGrow: 0,
}}
/>
</View>
</View>
<View
style={{
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
alignItems: 'center',
gap: 10,
}}
>
<ScrollView
horizontal={true}
style={{
paddingBottom: 10,
maxWidth: 1200,
}}
>
<View
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: 10,
}}
>
{!artist &&
artistsQuery.data?.map((artist, index) => (
<ArtistChipComponent
key={index}
name={artist.name}
onPress={() => {
setArtist(artist.name);
}}
/>
))}
</View>
</ScrollView>
<View>
<Select
selectedValue={genre?.name}
placeholder={translate('genreFilter')}
accessibilityLabel="Genre"
onValueChange={(itemValue) => {
setGenre(
genresQuery.data?.find((genre) => {
genre.name == itemValue;
})
);
}}
>
<Select.Item label={translate('emptySelection')} value="" />
{genresQuery.data?.map((data, index) => (
<Select.Item key={index} label={data.name} value={data.name} />
))}
</Select>
</View>
</View>
</View>
);
};
export default SearchBarComponent;

View File

@@ -84,6 +84,7 @@ export const en = {
songsFilter: 'Songs',
genreFilter: 'Genres',
favoriteFilter: 'Favorites',
searchBarPlaceholder: 'What are you looking for ?',
// profile page
user: 'Profile',
@@ -306,6 +307,7 @@ export const en = {
leaderBoardHeading: 'These are the best players',
leaderBoardHeadingFull:
'The players having the best scores, thanks to their exceptional accuracy, are highlighted here.',
emptySelection: 'None,',
};
export const fr: typeof en = {
@@ -395,6 +397,7 @@ export const fr: typeof en = {
songsFilter: 'Morceaux',
genreFilter: 'Genres',
favoriteFilter: 'Favoris',
searchBarPlaceholder: 'Que recherchez vous ?',
// Difficulty settings
diffBtn: 'Difficulté',
@@ -617,6 +620,7 @@ export const fr: typeof en = {
leaderBoardHeading: 'Voici les meilleurs joueurs',
leaderBoardHeadingFull:
'Les joueurs présentant les meilleurs scores, grâce à leur précision exceptionnelle, sont mis en lumière ici.',
emptySelection: 'Aucun',
};
export const sp: typeof en = {
@@ -720,6 +724,7 @@ export const sp: typeof en = {
songsFilter: 'canciones',
genreFilter: 'géneros',
favoriteFilter: 'Favorites',
searchBarPlaceholder: 'Qué estás buscando ?',
// Difficulty settings
diffBtn: 'Dificultad',
@@ -934,4 +939,5 @@ export const sp: typeof en = {
leaderBoardHeading: 'Estos son los mejores jugadores.',
leaderBoardHeadingFull:
'Aquí se destacan los jugadores que tienen las mejores puntuaciones, gracias a su precisión excepcional.',
emptySelection: 'Nada',
};

View File

@@ -93,7 +93,7 @@ const HomeView = (props: RouteProps<{}>) => {
label={'V2'}
colorScheme="gray"
size="sm"
onPress={() => navigation.navigate('HomeNew', {})}
onPress={() => navigation.navigate('Home', {})}
/>
</HStack>
<Box style={{ width: '100%' }}>
@@ -126,9 +126,7 @@ const HomeView = (props: RouteProps<{}>) => {
size="xs"
colorScheme="primary"
label={query}
onPress={() =>
navigation.navigate('Search', { query: query })
}
onPress={() => navigation.navigate('Search', {})}
/>
))}
</Flex>

View File

@@ -0,0 +1,15 @@
import React from 'react';
import ScaffoldCC from '../../components/UI/ScaffoldCC';
import SearchBarComponent from '../../components/V2/SearchBar';
import { RouteProps } from '../../Navigation';
// eslint-disable-next-line @typescript-eslint/ban-types
const SearchView = (props: RouteProps<{}>) => {
return (
<ScaffoldCC routeName={props.route.name}>
<SearchBarComponent />
</ScaffoldCC>
);
};
export default SearchView;