feat: front illustration, sorry for linting

This commit is contained in:
GitBluub
2023-05-30 02:09:08 +09:00
committed by Clément Le Bihan
parent 5843da2d02
commit 7f51582043
3 changed files with 288 additions and 192 deletions
+75 -47
View File
@@ -151,7 +151,9 @@ export default class API {
return response.access_token;
}
public static async transformGuestToUser(registrationInput: RegistrationInput): Promise<void> {
public static async transformGuestToUser(
registrationInput: RegistrationInput
): Promise<void> {
await API.fetch({
route: "/auth/me",
body: registrationInput,
@@ -180,7 +182,7 @@ export default class API {
createdAt: new Date("2023-04-09T00:00:00.000Z"),
avatar:
"https://imgs.search.brave.com/RnQpFhmAFvuQsN_xTw7V-CN61VeHDBg2tkEXnKRYHAE/rs:fit:768:512:1/g:ce/aHR0cHM6Ly96b29h/c3Ryby5jb20vd3At/Y29udGVudC91cGxv/YWRzLzIwMjEvMDIv/Q2FzdG9yLTc2OHg1/MTIuanBn",
}
},
} as User;
}
@@ -194,16 +196,18 @@ export default class API {
pushNotif: settings.pushNotification,
emailNotif: settings.emailNotification,
trainNotif: settings.trainingNotification,
newSongNotif: settings.newSongNotification
newSongNotif: settings.newSongNotification,
},
recommendations: settings.recommendations,
weeklyReport: settings.weeklyReport,
leaderBoard: settings.leaderBoard,
showActivity: settings.showActivity
showActivity: settings.showActivity,
};
}
public static async updateUserSettings(settings: PartialDeep<UserSettings>): Promise<void> {
public static async updateUserSettings(
settings: PartialDeep<UserSettings>
): Promise<void> {
const dto = {
pushNotification: settings.notifications?.pushNotif,
emailNotification: settings.notifications?.emailNotif,
@@ -213,11 +217,11 @@ export default class API {
weeklyReport: settings.weeklyReport,
leaderBoard: settings.leaderBoard,
showActivity: settings.showActivity,
}
};
return API.fetch({
method: 'PATCH',
route: '/auth/me/settings',
body: dto
method: "PATCH",
route: "/auth/me/settings",
body: dto,
});
}
@@ -248,16 +252,16 @@ export default class API {
// this is a dummy illustration, we will need to fetch the real one from the API
return songs.data.map(
(song: any) =>
({
id: song.id as number,
name: song.name as string,
artistId: song.artistId as number,
albumId: song.albumId as number,
genreId: song.genreId as number,
details: song.difficulties,
cover: getDummyIllustration(),
metrics: {},
} as Song)
({
id: song.id as number,
name: song.name as string,
artistId: song.artistId as number,
albumId: song.albumId as number,
genreId: song.genreId as number,
details: song.difficulties,
cover: getDummyIllustration(),
metrics: {},
} as Song)
);
}
@@ -292,6 +296,19 @@ export default class API {
});
}
/**
* Retrive a song's midi partition
* @param songId the id to find the song
*/
public static getGenreIllustration(genreId: number): string {
return `${baseAPIUrl}/genre/${genreId}/illustration`;
/*
return API.fetch({
route: `/genre/${genreId}/illustration`,
raw: true,
});*/
}
/**
* Retrive a song's musicXML partition
* @param songId the id to find the song
@@ -333,7 +350,9 @@ export default class API {
* Retrieve a song's play history
* @param songId the id to find the song
*/
public static async getSongHistory(songId: number): Promise<{best: number, history: SongHistory[]}> {
public static async getSongHistory(
songId: number
): Promise<{ best: number; history: SongHistory[] }> {
return API.fetch({
route: `/song/${songId}/history`,
});
@@ -365,22 +384,22 @@ export default class API {
*/
public static async searchAlbum(query?: string): Promise<Album[]> {
return [
{
id: 1,
name: "Super Trooper",
},
{
id: 2,
name: "Kingdom Heart 365/2 OST",
},
{
id: 3,
name: "The Legend Of Zelda Ocarina Of Time OST",
},
{
id: 4,
name: "Random Access Memories",
},
{
id: 1,
name: "Super Trooper",
},
{
id: 2,
name: "Kingdom Heart 365/2 OST",
},
{
id: 3,
name: "The Legend Of Zelda Ocarina Of Time OST",
},
{
id: 4,
name: "Random Access Memories",
},
] as Album[];
}
@@ -413,19 +432,24 @@ export default class API {
* @param take how much do we take to return
* @returns Returns an array of history entries (temporary type any)
*/
public static async getSearchHistory(skip?: number, take?: number): Promise<SearchHistory[]> {
return (await API.fetch({
route: `/history/search?skip=${skip ?? 0}&take=${take ?? 5}`,
method: "GET",
})).map((e: any) => {
public static async getSearchHistory(
skip?: number,
take?: number
): Promise<SearchHistory[]> {
return (
await API.fetch({
route: `/history/search?skip=${skip ?? 0}&take=${take ?? 5}`,
method: "GET",
})
).map((e: any) => {
return {
id: e.id,
query: e.query,
type: e.type,
userId: e.userId,
timestamp: new Date(e.searchDate),
} as SearchHistory
})
} as SearchHistory;
});
}
/**
@@ -435,15 +459,19 @@ export default class API {
* @param timestamp the date it's been issued
* @returns nothing
*/
public static async createSearchHistoryEntry(query: string, type: string, timestamp: number): Promise<void> {
public static async createSearchHistoryEntry(
query: string,
type: string,
timestamp: number
): Promise<void> {
return await API.fetch({
route: `/history/search`,
method: "POST",
body: {
query: query,
type: type
type: type,
},
})
});
}
/**
@@ -461,7 +489,7 @@ export default class API {
*/
public static async getUserPlayHistory(): Promise<SongHistory[]> {
return this.fetch({
route: '/history'
route: "/history",
});
}
+18 -16
View File
@@ -1,25 +1,22 @@
import React from "react";
import Card from './Card';
import { VStack, Text, Box, Icon } from 'native-base';
import Card from "./Card";
import { VStack, Text, Box, Icon, Image } from "native-base";
import { useTheme } from "native-base";
import { Ionicons } from "@expo/vector-icons";
import API from "../API";
type GenreCardProps = {
icon: string;
name: string;
id: number;
onPress: () => void;
}
};
const GenreCard = (props: GenreCardProps) => {
const { icon, name } = props;
const { icon, name, id } = props;
const theme = useTheme();
return (
<Card
shadow={3}
onPress={props.onPress}
>
<Card shadow={3} onPress={props.onPress}>
<VStack m={1.5} space={3} alignItems="center">
<Box
bg={theme.colors.primary[400]}
@@ -30,7 +27,12 @@ const GenreCard = (props: GenreCardProps) => {
alignItems="center"
justifyContent="center"
>
<Icon size={"md"} as={Ionicons} name={icon} />
<Image
source={{
uri: API.getGenreIllustration(id),
}}
size="md"
/>
</Box>
<VStack>
<Text isTruncated bold fontSize="md" noOfLines={2} height={50}>
@@ -40,12 +42,12 @@ const GenreCard = (props: GenreCardProps) => {
</VStack>
</Card>
);
}
};
GenreCard.defaultProps = {
icon: 'https://picsum.photos/200',
name: 'Genre',
onPress: () => { }
}
icon: "https://picsum.photos/200",
name: "Genre",
onPress: () => { },
};
export default GenreCard;
export default GenreCard;
+195 -129
View File
@@ -11,9 +11,10 @@ import {
Flex,
useBreakpointValue,
Column,
ScrollView} from "native-base";
ScrollView,
} from "native-base";
import { SafeAreaView, useColorScheme } from "react-native";
import { RootState, useSelector } from '../state/Store';
import { RootState, useSelector } from "../state/Store";
import { SearchContext } from "../views/SearchView";
import { useQuery } from "react-query";
import { translate } from "../i18n/i18n";
@@ -33,29 +34,39 @@ const swaToSongCardProps = (song: SongWithArtist) => ({
songId: song.id,
name: song.name,
artistName: song.artist.name,
cover: song.cover ?? 'https://picsum.photos/200',
cover: song.cover ?? "https://picsum.photos/200",
});
const RowCustom = (props: Parameters<typeof Box>[0] & { onPress?: () => void }) => {
const RowCustom = (
props: Parameters<typeof Box>[0] & { onPress?: () => void }
) => {
const settings = useSelector((state: RootState) => state.settings.local);
const systemColorMode = useColorScheme();
const colorScheme = settings.colorScheme;
return <Pressable onPress={props.onPress}>
{({ isHovered, isPressed }) => (
<Box {...props}
py={3}
my={1}
bg={(colorScheme == 'system' ? systemColorMode : colorScheme) == 'dark'
? (isHovered || isPressed) ? 'gray.800' : undefined
: (isHovered || isPressed) ? 'coolGray.200' : undefined
}
>
{ props.children }
</Box>
)}
</Pressable>
}
return (
<Pressable onPress={props.onPress}>
{({ isHovered, isPressed }) => (
<Box
{...props}
py={3}
my={1}
bg={
(colorScheme == "system" ? systemColorMode : colorScheme) == "dark"
? isHovered || isPressed
? "gray.800"
: undefined
: isHovered || isPressed
? "coolGray.200"
: undefined
}
>
{props.children}
</Box>
)}
</Pressable>
);
};
type SongRowProps = {
song: Song | SongWithArtist; // TODO: remove Song
@@ -63,155 +74,205 @@ type SongRowProps = {
};
const SongRow = ({ song, onPress }: SongRowProps) => {
return (
<RowCustom width={"100%"}>
<HStack px={2} space={5} justifyContent={"space-between"} >
<Image
flexShrink={0}
flexGrow={0}
pl={10}
style={{ zIndex: 0, aspectRatio: 1, borderRadius: 5 }}
source={{ uri: song.cover ?? 'https://picsum.photos/200' }}
alt={song.name}
/>
<HStack style={{display: 'flex', flexShrink: 1, flexGrow: 1, alignItems: 'center', justifyContent: "flex-start"}} space={6}>
<Text style={{
<HStack px={2} space={5} justifyContent={"space-between"}>
<Image
flexShrink={0}
flexGrow={0}
pl={10}
style={{ zIndex: 0, aspectRatio: 1, borderRadius: 5 }}
source={{ uri: song.cover ?? "https://picsum.photos/200" }}
alt={song.name}
/>
<HStack
style={{
display: "flex",
flexShrink: 1,
flexGrow: 1,
alignItems: "center",
justifyContent: "flex-start",
}}
space={6}
>
<Text
style={{
flexShrink: 1,
}} isTruncated pl={10} maxW={"100%"} bold fontSize='md'>{song.name}</Text>
<Text style={{
}}
isTruncated
pl={10}
maxW={"100%"}
bold
fontSize="md"
>
{song.name}
</Text>
<Text
style={{
flexShrink: 0,
}} fontSize={"sm"}>{song.artistId ?? 'artist'}</Text>
</HStack>
<TextButton
flexShrink={0}
flexGrow={0}
translate={{ translationKey: 'playBtn' }}
colorScheme='primary' variant={"outline"} size='sm'
onPress={onPress}
/>
}}
fontSize={"sm"}
>
{song.artistId ?? "artist"}
</Text>
</HStack>
<TextButton
flexShrink={0}
flexGrow={0}
translate={{ translationKey: "playBtn" }}
colorScheme="primary"
variant={"outline"}
size="sm"
onPress={onPress}
/>
</HStack>
</RowCustom>
);
}
};
SongRow.defaultProps = {
onPress: () => {},
onPress: () => { },
};
const HomeSearchComponent = () => {
const {stringQuery, updateStringQuery} = React.useContext(SearchContext);
const {isLoading: isLoadingHistory, data: historyData = []} = useQuery(
'history',
() => API.getSearchHistory(0, 12),
{ enabled: true },
);
const { stringQuery, updateStringQuery } = React.useContext(SearchContext);
const { isLoading: isLoadingHistory, data: historyData = [] } = useQuery(
"history",
() => API.getSearchHistory(0, 12),
{ enabled: true }
);
const {isLoading: isLoadingSuggestions, data: suggestionsData = []} = useQuery(
'suggestions',
() => getSongWArtistSuggestions(),
{ enabled: true },
);
const { isLoading: isLoadingSuggestions, data: suggestionsData = [] } =
useQuery("suggestions", () => getSongWArtistSuggestions(), {
enabled: true,
});
return (
<VStack mt="5" style={{overflow: 'hidden'}}>
<VStack mt="5" style={{ overflow: "hidden" }}>
<Card shadow={3} mb={5}>
<Heading margin={5}>{translate('lastSearched')}</Heading>
{ isLoadingHistory ? <LoadingComponent/> : <CardGridCustom content={historyData.map((h) => {
return {
...h,
timestamp: h.timestamp.toLocaleString(),
onPress: () => {updateStringQuery(h.query)}
}
})} cardComponent={SearchHistoryCard}/> }
<Heading margin={5}>{translate("lastSearched")}</Heading>
{isLoadingHistory ? (
<LoadingComponent />
) : (
<CardGridCustom
content={historyData.map((h) => {
return {
...h,
timestamp: h.timestamp.toLocaleString(),
onPress: () => {
updateStringQuery(h.query);
},
};
})}
cardComponent={SearchHistoryCard}
/>
)}
</Card>
<Card shadow={3} mt={5} mb={5}>
<Heading margin={5}>{translate('songsToGetBetter')}</Heading>
{ isLoadingSuggestions ? <LoadingComponent/> : <CardGridCustom content={suggestionsData.map(swaToSongCardProps)} cardComponent={SongCard}/> }
<Heading margin={5}>{translate("songsToGetBetter")}</Heading>
{isLoadingSuggestions ? (
<LoadingComponent />
) : (
<CardGridCustom
content={suggestionsData.map(swaToSongCardProps)}
cardComponent={SongCard}
/>
)}
</Card>
</VStack>
);
}
};
const SongsSearchComponent = (props: any) => {
const {songData} = React.useContext(SearchContext);
const { songData } = React.useContext(SearchContext);
const navigation = useNavigation();
return (
<ScrollView>
<Text fontSize="xl" fontWeight="bold" mt={4}>
{translate('songsFilter')}
</Text>
<Box>
{songData?.length ? (
songData.slice(0, props.maxRows).map((comp, index) => (
<SongRow
key={index}
song={comp}
onPress={() => {
API.createSearchHistoryEntry(comp.name, "song", Date.now());
navigation.navigate('Song', { songId: comp.id });
}}
/>
))
) : (
<Text>{translate('errNoResults')}</Text>
)}
</Box>
</ScrollView>
<ScrollView>
<Text fontSize="xl" fontWeight="bold" mt={4}>
{translate("songsFilter")}
</Text>
<Box>
{songData?.length ? (
songData.slice(0, props.maxRows).map((comp, index) => (
<SongRow
key={index}
song={comp}
onPress={() => {
API.createSearchHistoryEntry(comp.name, "song", Date.now());
navigation.navigate("Song", { songId: comp.id });
}}
/>
))
) : (
<Text>{translate("errNoResults")}</Text>
)}
</Box>
</ScrollView>
);
}
};
const ArtistSearchComponent = (props: any) => {
const {artistData} = React.useContext(SearchContext);
const { artistData } = React.useContext(SearchContext);
const navigation = useNavigation();
return (
<Box>
<Text fontSize="xl" fontWeight="bold" mt={4}>
{translate('artistFilter')}
{translate("artistFilter")}
</Text>
{ artistData?.length
? <CardGridCustom content={artistData.slice(0, props?.maxItems ?? artistData.length).map((a) => (
{
image: a.picture ?? 'https://picsum.photos/200',
name: a.name,
id: a.id,
onPress: () => {
API.createSearchHistoryEntry(a.name, "artist", Date.now());
navigation.navigate('Artist', { artistId: a.id })
}
}
))} cardComponent={ArtistCard} />
: <Text>{translate('errNoResults')}</Text> }
{artistData?.length ? (
<CardGridCustom
content={artistData
.slice(0, props?.maxItems ?? artistData.length)
.map((a) => ({
image: a.picture ?? "https://picsum.photos/200",
name: a.name,
id: a.id,
onPress: () => {
API.createSearchHistoryEntry(a.name, "artist", Date.now());
navigation.navigate("Artist", { artistId: a.id });
},
}))}
cardComponent={ArtistCard}
/>
) : (
<Text>{translate("errNoResults")}</Text>
)}
</Box>
);
}
};
const GenreSearchComponent = (props: any) => {
const {genreData} = React.useContext(SearchContext);
const { genreData } = React.useContext(SearchContext);
const navigation = useNavigation();
return (
<Box>
<Text fontSize="xl" fontWeight="bold" mt={4}>
{translate('genreFilter')}
{translate("genreFilter")}
</Text>
{ genreData?.length
? <CardGridCustom content={genreData.slice(0, props?.maxItems ?? genreData.length).map((g) => (
{
icon: 'musical-note-sharp',
name: g.name,
onPress: () => {
API.createSearchHistoryEntry(g.name, "genre", Date.now());
navigation.navigate('Home');
}
}
))} cardComponent={GenreCard}/>
: <Text>{translate('errNoResults')}</Text> }
{genreData?.length ? (
<CardGridCustom
content={genreData
.slice(0, props?.maxItems ?? genreData.length)
.map((g) => ({
icon: "musical-note-sharp",
name: g.name,
id: g.id,
onPress: () => {
API.createSearchHistoryEntry(g.name, "genre", Date.now());
navigation.navigate("Home");
},
}))}
cardComponent={GenreCard}
/>
) : (
<Text>{translate("errNoResults")}</Text>
)}
</Box>
);
}
};
const AllComponent = () => {
const screenSize = useBreakpointValue({ base: "small", md: "big" });
@@ -219,22 +280,27 @@ const AllComponent = () => {
return (
<SafeAreaView>
<Flex flexWrap="wrap" direction={isMobileView ? 'column' : 'row'} justifyContent={['flex-start']} mt={4}>
<Column w={isMobileView ? '100%' : '50%'}>
<Flex
flexWrap="wrap"
direction={isMobileView ? "column" : "row"}
justifyContent={["flex-start"]}
mt={4}
>
<Column w={isMobileView ? "100%" : "50%"}>
<Box minH={isMobileView ? 100 : 200}>
<ArtistSearchComponent maxItems={6}/>
<ArtistSearchComponent maxItems={6} />
</Box>
<Box minH={isMobileView ? 100 : 200}>
<GenreSearchComponent maxItems={6}/>
<GenreSearchComponent maxItems={6} />
</Box>
</Column>
<Box w={isMobileView ? '100%' : '50%'}>
<SongsSearchComponent maxRows={9}/>
<Box w={isMobileView ? "100%" : "50%"}>
<SongsSearchComponent maxRows={9} />
</Box>
</Flex>
</SafeAreaView>
);
}
};
const FilterSwitch = () => {
const { filter } = React.useContext(SearchContext);
@@ -270,4 +336,4 @@ export const SearchResultComponent = (props: any) => {
) : (
<HomeSearchComponent />
);
};
};