Front: Score Modal
This commit is contained in:
@@ -15,7 +15,6 @@ import SettingsTab from './views/settings/SettingsView';
|
||||
import { useQuery } from './Queries';
|
||||
import API, { APIError } from './API';
|
||||
import PlayView from './views/PlayView';
|
||||
import ScoreView from './views/ScoreView';
|
||||
import { LoadingView } from './components/Loading';
|
||||
import ProfileView from './views/ProfileView';
|
||||
import useColorScheme from './hooks/colorScheme';
|
||||
@@ -77,11 +76,6 @@ const protectedRoutes = () =>
|
||||
options: { title: translate('genreFilter') },
|
||||
link: '/genre/:genreId',
|
||||
},
|
||||
Score: {
|
||||
component: ScoreView,
|
||||
options: { title: translate('score'), headerLeft: null },
|
||||
link: undefined,
|
||||
},
|
||||
Search: {
|
||||
component: SearchView,
|
||||
options: { headerShown: false },
|
||||
|
||||
102
front/components/ScoreModal.tsx
Normal file
102
front/components/ScoreModal.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { Column, Row, Text, useTheme } from 'native-base';
|
||||
import ButtonBase from './UI/ButtonBase';
|
||||
import { Translate, TranslationKey, translate } from '../i18n/i18n';
|
||||
import { Play, Star1 } from 'iconsax-react-native';
|
||||
import { useNavigation } from '../Navigation';
|
||||
|
||||
type ScoreModalProps = {
|
||||
songId: number;
|
||||
overallScore: number;
|
||||
precision: number;
|
||||
score: {
|
||||
missed: number;
|
||||
good: number;
|
||||
great: number;
|
||||
perfect: number;
|
||||
wrong: number;
|
||||
max_score: number;
|
||||
current_streak: number;
|
||||
max_streak: number;
|
||||
};
|
||||
};
|
||||
|
||||
const ScoreModal = (props: ScoreModalProps) => {
|
||||
// const props = {
|
||||
// songId: 1,
|
||||
// overallScore: 74,
|
||||
// precision: 0,
|
||||
// score: {
|
||||
// missed: 9,
|
||||
// good: 1,
|
||||
// great: 2,
|
||||
// perfect: 4,
|
||||
// wrong: 0,
|
||||
// max_score: 100,
|
||||
// current_streak: 1,
|
||||
// max_streak: 11,
|
||||
// } as const
|
||||
// } as const; //TODO DELETE ME
|
||||
const navigation = useNavigation()
|
||||
const theme = useTheme();
|
||||
const score = (props.overallScore * 100) / props.score.max_score
|
||||
const column1 = {
|
||||
perfect: [props.score.perfect, 'primary'],
|
||||
great: [props.score.great, 'secondary'],
|
||||
good: [props.score.good, 'success']
|
||||
} as const
|
||||
const column2 = {
|
||||
bestStreak: [props.score.max_streak, 'notification'],
|
||||
missed: [props.score.missed, 'alert'],
|
||||
wrong: [props.score.wrong, 'error']
|
||||
} as const
|
||||
|
||||
return <Column space={4} style={{ alignItems: 'center' }}>
|
||||
<Row space={2} style={{ justifyContent: 'center' }}>
|
||||
{[1, 2, 3].map((index) => (
|
||||
<Star1
|
||||
color={theme.colors.primary[500]}
|
||||
key={index}
|
||||
variant={score >= (index * 100/ 4) ? 'Bold' : 'Outline' }
|
||||
/>
|
||||
))}
|
||||
</Row>
|
||||
<Text fontSize='3xl' >{score}%</Text>
|
||||
<Row w="100%" style={{ justifyContent: 'space-between' }}>
|
||||
<Translate translationKey='precision'/>
|
||||
<Text>{props.precision}%</Text>
|
||||
</Row>
|
||||
<Row w="100%" space={2}>
|
||||
{([column1, column2] as const).map((column, columnIndex) => (
|
||||
<Column w="50%" space={2} key={columnIndex}>
|
||||
{Object.keys(column).map((key) => {
|
||||
const translationKey = key;
|
||||
const [value, color] = column[translationKey as keyof typeof column] as [number, string];
|
||||
|
||||
return <Row key={translationKey} style={{ justifyContent: 'space-between' }}>
|
||||
<Translate translationKey={translationKey as TranslationKey} fontWeight={'bold'} color={`${color}.500`} />
|
||||
<Text>x{value}</Text>
|
||||
</Row>
|
||||
})}
|
||||
</Column>
|
||||
))}
|
||||
</Row>
|
||||
<Row style={{ justifyContent: 'space-between' }}>
|
||||
<ButtonBase
|
||||
style={{}}
|
||||
icon={Play}
|
||||
type="outlined"
|
||||
title={translate('playAgain')}
|
||||
onPress={() => navigation.navigate('Play', { songId: props.songId })}
|
||||
/>
|
||||
<ButtonBase
|
||||
style={{}}
|
||||
icon={Play}
|
||||
type="filled"
|
||||
title={translate('menuMusic')}
|
||||
onPress={() => navigation.goBack()}
|
||||
/>
|
||||
</Row>
|
||||
</Column>
|
||||
};
|
||||
|
||||
export default ScoreModal;
|
||||
@@ -7,7 +7,7 @@ import React from 'react';
|
||||
import GlassmorphismCC from './Glassmorphism';
|
||||
|
||||
type PopupCCProps = {
|
||||
title: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
children?: ReactNode;
|
||||
isVisible: boolean;
|
||||
@@ -34,7 +34,7 @@ const PopupCC = ({ title, description, children, isVisible, setIsVisible }: Popu
|
||||
}}
|
||||
space={4}
|
||||
>
|
||||
<Heading size="md" mb={2} alignItems={'flex-end'}>
|
||||
{(setIsVisible || title) && <Heading size="md" mb={2} alignItems={'flex-end'}>
|
||||
<Row style={{ flex: 1, width: '100%', alignItems: 'flex-end' }}>
|
||||
<Text style={{ flex: 1, width: '100%' }}>{title}</Text>
|
||||
{setIsVisible !== undefined && (
|
||||
@@ -45,7 +45,7 @@ const PopupCC = ({ title, description, children, isVisible, setIsVisible }: Popu
|
||||
/>
|
||||
)}
|
||||
</Row>
|
||||
</Heading>
|
||||
</Heading>}
|
||||
{description !== undefined && <Text>{description}</Text>}
|
||||
{children !== undefined && children}
|
||||
</Column>
|
||||
|
||||
@@ -3,7 +3,7 @@ import useColorScheme from '../../hooks/colorScheme';
|
||||
import { useQuery } from '../../Queries';
|
||||
import API from '../../API';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { Cup, Discover, Music, SearchNormal1, Setting2, User } from 'iconsax-react-native';
|
||||
import { Discover, Music, SearchNormal1, Setting2, User } from 'iconsax-react-native';
|
||||
import { LoadingView } from '../Loading';
|
||||
import ScaffoldDesktopCC from './ScaffoldDesktopCC';
|
||||
import ScaffoldMobileCC from './ScaffoldMobileCC';
|
||||
@@ -13,7 +13,6 @@ const menu = [
|
||||
{ type: 'main', title: 'menuProfile', icon: User, link: 'User' },
|
||||
{ type: 'main', title: 'menuMusic', icon: Music, link: 'Music' },
|
||||
{ type: 'main', title: 'menuSearch', icon: SearchNormal1, link: 'Search' },
|
||||
{ type: 'main', title: 'menuLeaderBoard', icon: Cup, link: 'Score' },
|
||||
{ type: 'sub', title: 'menuSettings', icon: Setting2, link: 'Settings' },
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import PopupCC from '../components/UI/PopupCC';
|
||||
import ButtonBase from '../components/UI/ButtonBase';
|
||||
import { Clock, Cup } from 'iconsax-react-native';
|
||||
import PlayViewControlBar from '../components/Play/PlayViewControlBar';
|
||||
import ScoreModal from '../components/ScoreModal';
|
||||
|
||||
type PlayViewProps = {
|
||||
songId: number;
|
||||
@@ -88,6 +89,7 @@ const PlayView = ({ songId, route }: RouteProps<PlayViewProps>) => {
|
||||
const [paused, setPause] = useState<boolean>(true);
|
||||
const stopwatch = useStopwatch();
|
||||
const [time, setTime] = useState(0);
|
||||
const [endResult, setEndResult] = useState();
|
||||
const songHistory = useQuery(API.getSongHistory(songId));
|
||||
const [partitionRendered, setPartitionRendered] = useState(false); // Used to know when partitionview can render
|
||||
const [score, setScore] = useState(0); // Between 0 and 100
|
||||
@@ -186,11 +188,10 @@ const PlayView = ({ songId, route }: RouteProps<PlayViewProps>) => {
|
||||
if (data.type == 'end') {
|
||||
endMsgReceived = true;
|
||||
webSocket.current?.close();
|
||||
navigation.dispatch(
|
||||
StackActions.replace('Score', { songId: song.data!.id, ...data })
|
||||
);
|
||||
setEndResult({ songId: song.data!.id, ...data });
|
||||
return;
|
||||
}
|
||||
console.log(data);
|
||||
|
||||
const points = data.info.score;
|
||||
const maxPoints = data.info.max_score || 1;
|
||||
@@ -332,10 +333,15 @@ const PlayView = ({ songId, route }: RouteProps<PlayViewProps>) => {
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
<PopupCC
|
||||
isVisible={endResult != undefined}
|
||||
>{
|
||||
(() => endResult ? <ScoreModal {...endResult}/> : <></>)()
|
||||
}</PopupCC>
|
||||
<PopupCC
|
||||
title={translate('selectPlayMode')}
|
||||
description={translate('selectPlayModeExplaination')}
|
||||
isVisible={type === undefined}
|
||||
isVisible={false}
|
||||
setIsVisible={
|
||||
navigation.canGoBack()
|
||||
? (isVisible) => {
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
import { Card, Column, Image, Row, Text, ScrollView, VStack } from 'native-base';
|
||||
import Translate from '../components/Translate';
|
||||
import { RouteProps, useNavigation } from '../Navigation';
|
||||
import { CardBorderRadius } from '../components/Card';
|
||||
import TextButton from '../components/TextButton';
|
||||
import API from '../API';
|
||||
import CardGridCustom from '../components/CardGridCustom';
|
||||
import SongCard from '../components/SongCard';
|
||||
import { useQueries, useQuery } from '../Queries';
|
||||
import { LoadingView } from '../components/Loading';
|
||||
import ScoreGraph from '../components/ScoreGraph';
|
||||
|
||||
type ScoreViewProps = {
|
||||
songId: number;
|
||||
overallScore: number;
|
||||
precision: number;
|
||||
score: {
|
||||
missed: number;
|
||||
good: number;
|
||||
great: number;
|
||||
perfect: number;
|
||||
wrong: number;
|
||||
max_score: number;
|
||||
current_streak: number;
|
||||
max_streak: number;
|
||||
};
|
||||
};
|
||||
|
||||
const ScoreView = (props: RouteProps<ScoreViewProps>) => {
|
||||
const { songId, overallScore, precision, score } = props;
|
||||
const navigation = useNavigation();
|
||||
const songQuery = useQuery(API.getSong(songId));
|
||||
const artistQuery = useQuery(() => API.getArtist(songQuery.data!.artistId!), {
|
||||
enabled: songQuery.data !== undefined,
|
||||
});
|
||||
const recommendations = useQuery(API.getSongSuggestions);
|
||||
const artistRecommendations = useQueries(
|
||||
recommendations.data
|
||||
?.filter(({ artistId }) => artistId !== null)
|
||||
.map((song) => API.getArtist(song.artistId)) ?? []
|
||||
);
|
||||
|
||||
if (
|
||||
!recommendations.data ||
|
||||
artistRecommendations.find(({ data }) => !data) ||
|
||||
!songQuery.data ||
|
||||
(songQuery.data.artistId && !artistQuery.data)
|
||||
) {
|
||||
return <LoadingView />;
|
||||
}
|
||||
if (songQuery.isError) {
|
||||
navigation.navigate('Error');
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView p={8} contentContainerStyle={{ alignItems: 'center' }}>
|
||||
<VStack width={{ base: '100%', lg: '50%' }} space={3} textAlign="center">
|
||||
<Text bold fontSize="lg">
|
||||
{songQuery.data.name}
|
||||
</Text>
|
||||
<Text bold>{artistQuery.data?.name}</Text>
|
||||
<Row style={{ justifyContent: 'center', display: 'flex' }}>
|
||||
<Card shadow={3} style={{ flex: 1 }}>
|
||||
<Image
|
||||
style={{
|
||||
zIndex: 0,
|
||||
aspectRatio: 1,
|
||||
margin: 5,
|
||||
borderRadius: CardBorderRadius,
|
||||
}}
|
||||
source={{ uri: songQuery.data.cover }}
|
||||
/>
|
||||
</Card>
|
||||
<Card shadow={3} style={{ flex: 1 }}>
|
||||
<Column style={{ justifyContent: 'space-evenly', flexGrow: 1 }}>
|
||||
{/*<Row style={{ alignItems: 'center' }}>
|
||||
<Text bold fontSize='xl'>
|
||||
|
||||
</Text>
|
||||
<Translate translationKey='goodNotes' format={(t) => ' ' + t}/>
|
||||
</Row>
|
||||
<Row style={{ alignItems: 'center' }}>
|
||||
<Text bold fontSize='xl'>
|
||||
80
|
||||
</Text>
|
||||
<Translate translationKey='goodNotesInARow' format={(t) => ' ' + t}/>
|
||||
</Row>*/}
|
||||
<Row style={{ alignItems: 'center' }}>
|
||||
<Translate translationKey="score" format={(t) => t + ' : '} />
|
||||
<Text bold fontSize="xl">
|
||||
{overallScore + 'pts'}
|
||||
</Text>
|
||||
</Row>
|
||||
<Row style={{ alignItems: 'center' }}>
|
||||
<Translate translationKey="perfect" format={(t) => t + ' : '} />
|
||||
<Text bold fontSize="xl">
|
||||
{score.perfect}
|
||||
</Text>
|
||||
</Row>
|
||||
<Row style={{ alignItems: 'center' }}>
|
||||
<Translate translationKey="great" format={(t) => t + ' : '} />
|
||||
<Text bold fontSize="xl">
|
||||
{score.great}
|
||||
</Text>
|
||||
</Row>
|
||||
<Row style={{ alignItems: 'center' }}>
|
||||
<Translate translationKey="good" format={(t) => t + ' : '} />
|
||||
<Text bold fontSize="xl">
|
||||
{score.good}
|
||||
</Text>
|
||||
</Row>
|
||||
<Row style={{ alignItems: 'center' }}>
|
||||
<Translate translationKey="wrong" format={(t) => t + ' : '} />
|
||||
<Text bold fontSize="xl">
|
||||
{score.wrong}
|
||||
</Text>
|
||||
</Row>
|
||||
|
||||
<Row style={{ alignItems: 'center' }}>
|
||||
<Translate translationKey="missed" format={(t) => t + ' : '} />
|
||||
<Text bold fontSize="xl">
|
||||
{score.missed}
|
||||
</Text>
|
||||
</Row>
|
||||
<Row style={{ alignItems: 'center' }}>
|
||||
<Translate translationKey="bestStreak" format={(t) => t + ' : '} />
|
||||
<Text bold fontSize="xl">
|
||||
{score.max_streak}
|
||||
</Text>
|
||||
</Row>
|
||||
<Row style={{ alignItems: 'center' }}>
|
||||
<Translate translationKey="precision" format={(t) => t + ' : '} />
|
||||
<Text bold fontSize="xl">
|
||||
{precision + '%'}
|
||||
</Text>
|
||||
</Row>
|
||||
</Column>
|
||||
</Card>
|
||||
</Row>
|
||||
<ScoreGraph />
|
||||
<CardGridCustom
|
||||
style={{ justifyContent: 'space-evenly' }}
|
||||
content={recommendations.data.map((i) => ({
|
||||
cover: i.cover,
|
||||
name: i.name,
|
||||
artistName:
|
||||
artistRecommendations.find(({ data }) => data?.id == i.artistId)?.data
|
||||
?.name ?? '',
|
||||
songId: i.id,
|
||||
}))}
|
||||
cardComponent={SongCard}
|
||||
heading={
|
||||
<Text fontSize="sm">
|
||||
<Translate translationKey="songsToGetBetter" />
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
<Row space={3} style={{ width: '100%', justifyContent: 'center' }}>
|
||||
<TextButton
|
||||
colorScheme="gray"
|
||||
translate={{ translationKey: 'backBtn' }}
|
||||
onPress={() => navigation.navigate('Home', {})}
|
||||
/>
|
||||
<TextButton
|
||||
onPress={() => navigation.navigate('Play', { songId })}
|
||||
translate={{ translationKey: 'playAgain' }}
|
||||
/>
|
||||
</Row>
|
||||
</VStack>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScoreView;
|
||||
Reference in New Issue
Block a user