Front: Use history include to get best/last score for a song

This commit is contained in:
Arthur Jamet
2023-12-19 11:13:25 +01:00
committed by Clément Le Bihan
parent b4f04f9b71
commit 7a6dc8b0c9
7 changed files with 38 additions and 35 deletions

View File

@@ -20,14 +20,11 @@ export interface MusicItemType {
/** The URL for the song's cover image. */
image: string;
/** The level of the song difficulty . */
level: number;
/** The last score achieved for this song. */
lastScore: number;
lastScore: number | null | undefined;
/** The highest score achieved for this song. */
bestScore: number;
bestScore: number | null | undefined;
/** Indicates whether the song is liked/favorited by the user. */
liked: boolean;
@@ -141,9 +138,8 @@ function MusicItemComponent(props: MusicItemType) {
);
// Memoizing formatted numbers to avoid unnecessary computations.
const formattedLevel = useMemo(() => formatNumber(props.level), [props.level]);
const formattedLastScore = useMemo(() => formatNumber(props.lastScore), [props.lastScore]);
const formattedBestScore = useMemo(() => formatNumber(props.bestScore), [props.bestScore]);
const formattedLastScore = useMemo(() => formatNumber(props.lastScore ?? 0), [props.lastScore]);
const formattedBestScore = useMemo(() => formatNumber(props.bestScore ?? 0), [props.bestScore]);
return (
<HStack space={screenSize === 'xl' ? 2 : 1} style={[styles.container, props.style]}>
@@ -179,7 +175,7 @@ function MusicItemComponent(props: MusicItemType) {
/>
</Row>
</Column>
{[formattedLevel, formattedLastScore, formattedBestScore].map((value, index) => (
{[formattedLastScore, formattedBestScore].map((value, index) => (
<Text key={index} style={styles.stats}>
{value}
</Text>

View File

@@ -3,7 +3,7 @@ import { FlatList, HStack, View, useBreakpointValue, useTheme, Text, Row } from
import { ActivityIndicator, StyleSheet } from 'react-native';
import MusicItem, { MusicItemType } from './MusicItem';
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';
// Props type definition for MusicItemTitle.
@@ -176,7 +176,6 @@ function MusicListComponent({
{translate('musicListTitleSong')}
</Text>
{[
{ text: translate('musicListTitleLevel'), icon: Chart2 },
{ text: translate('musicListTitleLastScore'), icon: ArrowRotateLeft },
{ text: translate('musicListTitleBestScore'), icon: Cup },
].map((value) => (

View File

@@ -10,21 +10,6 @@ type SongCardInfoProps = {
onPlay: () => void;
};
const Scores = [
{
icon: 'warning',
score: 3,
},
{
icon: 'star',
score: -225,
},
{
icon: 'trophy',
score: 100,
},
];
const SongCardInfo = (props: SongCardInfoProps) => {
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
const isPhone = screenSize === 'small';
@@ -37,6 +22,18 @@ const SongCardInfo = (props: SongCardInfoProps) => {
width: isPhone ? 160 : 200,
};
const Scores = [
{
icon: 'time',
score: props.song.lastScore ?? '?',
},
{
icon: 'trophy',
score: props.song.bestScore ?? '?',
},
];
return (
<View
style={{

View File

@@ -6,6 +6,7 @@ import ResponseHandler from './ResponseHandler';
import API from '../API';
import { AlbumValidator } from './Album';
import { GenreValidator } from './Genre';
import { SongHistoryItem, SongHistoryItemValidator } from './SongHistory';
export type SongInclude = 'artist' | 'album' | 'genre' | 'SongHistory' | 'likedByUsers';
@@ -20,20 +21,33 @@ export const SongValidator = yup
difficulties: SongDetailsValidator.required(),
details: SongDetailsValidator.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(),
album: yup.lazy(() => AlbumValidator.default(undefined)).optional(),
genre: yup.lazy(() => GenreValidator.default(undefined)).optional(),
})
.concat(ModelValidator)
.transform((song: Song) => ({
.transform((song: Song & { SongHistory: SongHistoryItem[] }) => ({
...song,
cover: `${API.baseUrl}/song/${song.id}/illustration`,
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 const SongHandler: ResponseHandler<Song> = {
export const SongHandler: ResponseHandler<yup.InferType<typeof SongValidator>> = {
validator: SongValidator,
};

View File

@@ -1,12 +1,10 @@
import * as yup from 'yup';
import ResponseHandler from './ResponseHandler';
import { ModelValidator } from './Model';
import { SongValidator } from './Song';
export const SongHistoryItemValidator = yup
.object({
songID: yup.number().required(),
song: SongValidator.optional().default(undefined),
userID: yup.number().required(),
info: yup
.object({

View File

@@ -21,16 +21,15 @@ import { LoadingView } from '../components/Loading';
export const FavoritesMusic = () => {
const navigation = useNavigation();
const likedSongs = useQuery(API.getLikedSongs(['artist']));
const likedSongs = useQuery(API.getLikedSongs(['artist', 'SongHistory']));
const musics =
likedSongs.data?.map((x) => ({
artist: x.song.artist!.name,
song: x.song.name,
image: x.song.cover,
level: 42,
lastScore: 42,
bestScore: 42,
lastScore: x.song.lastScore,
bestScore: x.song.bestScore,
liked: true,
onLike: () => {
console.log('onLike');

View File

@@ -10,7 +10,7 @@ import GoldenRatio from '../../components/V2/GoldenRatio';
// eslint-disable-next-line @typescript-eslint/ban-types
const HomeView = (props: RouteProps<{}>) => {
const suggestionsQuery = useQuery(API.getSongSuggestions(['artist']));
const suggestionsQuery = useQuery(API.getSongSuggestions(['artist', 'SongHistory']));
const navigation = useNavigation();
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
const isPhone = screenSize === 'small';