Front: Use history include to get best/last score for a song
This commit is contained in:
committed by
Clément Le Bihan
parent
b4f04f9b71
commit
7a6dc8b0c9
@@ -20,14 +20,11 @@ export interface MusicItemType {
|
|||||||
/** The URL for the song's cover image. */
|
/** The URL for the song's cover image. */
|
||||||
image: string;
|
image: string;
|
||||||
|
|
||||||
/** The level of the song difficulty . */
|
|
||||||
level: number;
|
|
||||||
|
|
||||||
/** The last score achieved for this song. */
|
/** The last score achieved for this song. */
|
||||||
lastScore: number;
|
lastScore: number | null | undefined;
|
||||||
|
|
||||||
/** The highest score achieved for this song. */
|
/** The highest score achieved for this song. */
|
||||||
bestScore: number;
|
bestScore: number | null | undefined;
|
||||||
|
|
||||||
/** Indicates whether the song is liked/favorited by the user. */
|
/** Indicates whether the song is liked/favorited by the user. */
|
||||||
liked: boolean;
|
liked: boolean;
|
||||||
@@ -141,9 +138,8 @@ function MusicItemComponent(props: MusicItemType) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Memoizing formatted numbers to avoid unnecessary computations.
|
// Memoizing formatted numbers to avoid unnecessary computations.
|
||||||
const formattedLevel = useMemo(() => formatNumber(props.level), [props.level]);
|
const formattedLastScore = useMemo(() => formatNumber(props.lastScore ?? 0), [props.lastScore]);
|
||||||
const formattedLastScore = useMemo(() => formatNumber(props.lastScore), [props.lastScore]);
|
const formattedBestScore = useMemo(() => formatNumber(props.bestScore ?? 0), [props.bestScore]);
|
||||||
const formattedBestScore = useMemo(() => formatNumber(props.bestScore), [props.bestScore]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack space={screenSize === 'xl' ? 2 : 1} style={[styles.container, props.style]}>
|
<HStack space={screenSize === 'xl' ? 2 : 1} style={[styles.container, props.style]}>
|
||||||
@@ -179,7 +175,7 @@ function MusicItemComponent(props: MusicItemType) {
|
|||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
</Column>
|
</Column>
|
||||||
{[formattedLevel, formattedLastScore, formattedBestScore].map((value, index) => (
|
{[formattedLastScore, formattedBestScore].map((value, index) => (
|
||||||
<Text key={index} style={styles.stats}>
|
<Text key={index} style={styles.stats}>
|
||||||
{value}
|
{value}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { FlatList, HStack, View, useBreakpointValue, useTheme, Text, Row } from
|
|||||||
import { ActivityIndicator, StyleSheet } from 'react-native';
|
import { ActivityIndicator, StyleSheet } from 'react-native';
|
||||||
import MusicItem, { MusicItemType } from './MusicItem';
|
import MusicItem, { MusicItemType } from './MusicItem';
|
||||||
import ButtonBase from './ButtonBase';
|
import ButtonBase from './ButtonBase';
|
||||||
import { ArrowDown2, Chart2, ArrowRotateLeft, Cup, Icon } from 'iconsax-react-native';
|
import { ArrowDown2, ArrowRotateLeft, Cup, Icon } from 'iconsax-react-native';
|
||||||
import { translate } from '../../i18n/i18n';
|
import { translate } from '../../i18n/i18n';
|
||||||
|
|
||||||
// Props type definition for MusicItemTitle.
|
// Props type definition for MusicItemTitle.
|
||||||
@@ -176,7 +176,6 @@ function MusicListComponent({
|
|||||||
{translate('musicListTitleSong')}
|
{translate('musicListTitleSong')}
|
||||||
</Text>
|
</Text>
|
||||||
{[
|
{[
|
||||||
{ text: translate('musicListTitleLevel'), icon: Chart2 },
|
|
||||||
{ text: translate('musicListTitleLastScore'), icon: ArrowRotateLeft },
|
{ text: translate('musicListTitleLastScore'), icon: ArrowRotateLeft },
|
||||||
{ text: translate('musicListTitleBestScore'), icon: Cup },
|
{ text: translate('musicListTitleBestScore'), icon: Cup },
|
||||||
].map((value) => (
|
].map((value) => (
|
||||||
|
|||||||
@@ -10,21 +10,6 @@ type SongCardInfoProps = {
|
|||||||
onPlay: () => void;
|
onPlay: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Scores = [
|
|
||||||
{
|
|
||||||
icon: 'warning',
|
|
||||||
score: 3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'star',
|
|
||||||
score: -225,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'trophy',
|
|
||||||
score: 100,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const SongCardInfo = (props: SongCardInfoProps) => {
|
const SongCardInfo = (props: SongCardInfoProps) => {
|
||||||
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
||||||
const isPhone = screenSize === 'small';
|
const isPhone = screenSize === 'small';
|
||||||
@@ -37,6 +22,18 @@ const SongCardInfo = (props: SongCardInfoProps) => {
|
|||||||
width: isPhone ? 160 : 200,
|
width: isPhone ? 160 : 200,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const Scores = [
|
||||||
|
{
|
||||||
|
icon: 'time',
|
||||||
|
score: props.song.lastScore ?? '?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'trophy',
|
||||||
|
score: props.song.bestScore ?? '?',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
+16
-2
@@ -6,6 +6,7 @@ import ResponseHandler from './ResponseHandler';
|
|||||||
import API from '../API';
|
import API from '../API';
|
||||||
import { AlbumValidator } from './Album';
|
import { AlbumValidator } from './Album';
|
||||||
import { GenreValidator } from './Genre';
|
import { GenreValidator } from './Genre';
|
||||||
|
import { SongHistoryItem, SongHistoryItemValidator } from './SongHistory';
|
||||||
|
|
||||||
export type SongInclude = 'artist' | 'album' | 'genre' | 'SongHistory' | 'likedByUsers';
|
export type SongInclude = 'artist' | 'album' | 'genre' | 'SongHistory' | 'likedByUsers';
|
||||||
|
|
||||||
@@ -20,20 +21,33 @@ export const SongValidator = yup
|
|||||||
difficulties: SongDetailsValidator.required(),
|
difficulties: SongDetailsValidator.required(),
|
||||||
details: SongDetailsValidator.required(),
|
details: SongDetailsValidator.required(),
|
||||||
cover: yup.string().required(),
|
cover: yup.string().required(),
|
||||||
|
SongHistory: yup
|
||||||
|
.lazy(() => yup.array(SongHistoryItemValidator.default(undefined)))
|
||||||
|
.optional(),
|
||||||
|
bestScore: yup.number().optional().nullable(),
|
||||||
|
lastScore: yup.number().optional().nullable(),
|
||||||
artist: yup.lazy(() => ArtistValidator.default(undefined)).optional(),
|
artist: yup.lazy(() => ArtistValidator.default(undefined)).optional(),
|
||||||
album: yup.lazy(() => AlbumValidator.default(undefined)).optional(),
|
album: yup.lazy(() => AlbumValidator.default(undefined)).optional(),
|
||||||
genre: yup.lazy(() => GenreValidator.default(undefined)).optional(),
|
genre: yup.lazy(() => GenreValidator.default(undefined)).optional(),
|
||||||
})
|
})
|
||||||
.concat(ModelValidator)
|
.concat(ModelValidator)
|
||||||
.transform((song: Song) => ({
|
.transform((song: Song & { SongHistory: SongHistoryItem[] }) => ({
|
||||||
...song,
|
...song,
|
||||||
cover: `${API.baseUrl}/song/${song.id}/illustration`,
|
cover: `${API.baseUrl}/song/${song.id}/illustration`,
|
||||||
details: song.difficulties,
|
details: song.difficulties,
|
||||||
|
bestScore:
|
||||||
|
song.SongHistory?.map(({ info }) => info.score)
|
||||||
|
.sort()
|
||||||
|
.at(-1) ?? null,
|
||||||
|
lastScore:
|
||||||
|
song.SongHistory?.map(({ info, playDate }) => ({ info, playDate }))
|
||||||
|
.sort((a, b) => yup.date().cast(a.playDate)!.getTime() - yup.date().cast(b.playDate)!.getTime())
|
||||||
|
.at(0)?.info.score ?? null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export type Song = yup.InferType<typeof SongValidator>;
|
export type Song = yup.InferType<typeof SongValidator>;
|
||||||
|
|
||||||
export const SongHandler: ResponseHandler<Song> = {
|
export const SongHandler: ResponseHandler<yup.InferType<typeof SongValidator>> = {
|
||||||
validator: SongValidator,
|
validator: SongValidator,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
import ResponseHandler from './ResponseHandler';
|
import ResponseHandler from './ResponseHandler';
|
||||||
import { ModelValidator } from './Model';
|
import { ModelValidator } from './Model';
|
||||||
import { SongValidator } from './Song';
|
|
||||||
|
|
||||||
export const SongHistoryItemValidator = yup
|
export const SongHistoryItemValidator = yup
|
||||||
.object({
|
.object({
|
||||||
songID: yup.number().required(),
|
songID: yup.number().required(),
|
||||||
song: SongValidator.optional().default(undefined),
|
|
||||||
userID: yup.number().required(),
|
userID: yup.number().required(),
|
||||||
info: yup
|
info: yup
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -21,16 +21,15 @@ import { LoadingView } from '../components/Loading';
|
|||||||
|
|
||||||
export const FavoritesMusic = () => {
|
export const FavoritesMusic = () => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const likedSongs = useQuery(API.getLikedSongs(['artist']));
|
const likedSongs = useQuery(API.getLikedSongs(['artist', 'SongHistory']));
|
||||||
|
|
||||||
const musics =
|
const musics =
|
||||||
likedSongs.data?.map((x) => ({
|
likedSongs.data?.map((x) => ({
|
||||||
artist: x.song.artist!.name,
|
artist: x.song.artist!.name,
|
||||||
song: x.song.name,
|
song: x.song.name,
|
||||||
image: x.song.cover,
|
image: x.song.cover,
|
||||||
level: 42,
|
lastScore: x.song.lastScore,
|
||||||
lastScore: 42,
|
bestScore: x.song.bestScore,
|
||||||
bestScore: 42,
|
|
||||||
liked: true,
|
liked: true,
|
||||||
onLike: () => {
|
onLike: () => {
|
||||||
console.log('onLike');
|
console.log('onLike');
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import GoldenRatio from '../../components/V2/GoldenRatio';
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
const HomeView = (props: RouteProps<{}>) => {
|
const HomeView = (props: RouteProps<{}>) => {
|
||||||
const suggestionsQuery = useQuery(API.getSongSuggestions(['artist']));
|
const suggestionsQuery = useQuery(API.getSongSuggestions(['artist', 'SongHistory']));
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
||||||
const isPhone = screenSize === 'small';
|
const isPhone = screenSize === 'small';
|
||||||
|
|||||||
Reference in New Issue
Block a user