Merge remote-tracking branch 'origin/main' into feat/adc/search-view-v2
This commit is contained in:
@@ -36,6 +36,8 @@ services:
|
|||||||
- scoro_logs:/logs
|
- scoro_logs:/logs
|
||||||
networks:
|
networks:
|
||||||
- loki
|
- loki
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
db:
|
db:
|
||||||
container_name: db
|
container_name: db
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- scoro_logs:/logs
|
- scoro_logs:/logs
|
||||||
- ./assets:/assets
|
- ./assets:/assets
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
db:
|
db:
|
||||||
container_name: db
|
container_name: db
|
||||||
image: postgres:alpine3.14
|
image: postgres:alpine3.14
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./assets:/assets
|
- ./assets:/assets
|
||||||
- scoro_logs:/logs
|
- scoro_logs:/logs
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
db:
|
db:
|
||||||
container_name: db
|
container_name: db
|
||||||
image: postgres:alpine3.14
|
image: postgres:alpine3.14
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import SettingsTab from './views/settings/SettingsView';
|
|||||||
import { useQuery } from './Queries';
|
import { useQuery } from './Queries';
|
||||||
import API, { APIError } from './API';
|
import API, { APIError } from './API';
|
||||||
import PlayView from './views/PlayView';
|
import PlayView from './views/PlayView';
|
||||||
import ScoreView from './views/ScoreView';
|
|
||||||
import { LoadingView } from './components/Loading';
|
import { LoadingView } from './components/Loading';
|
||||||
import ProfileView from './views/ProfileView';
|
import ProfileView from './views/ProfileView';
|
||||||
import useColorScheme from './hooks/colorScheme';
|
import useColorScheme from './hooks/colorScheme';
|
||||||
@@ -79,11 +78,6 @@ const protectedRoutes = () =>
|
|||||||
options: { title: translate('genreFilter') },
|
options: { title: translate('genreFilter') },
|
||||||
link: '/genre/:genreId',
|
link: '/genre/:genreId',
|
||||||
},
|
},
|
||||||
Score: {
|
|
||||||
component: ScoreView,
|
|
||||||
options: { title: translate('score'), headerLeft: null },
|
|
||||||
link: undefined,
|
|
||||||
},
|
|
||||||
Search: {
|
Search: {
|
||||||
component: SearchView,
|
component: SearchView,
|
||||||
options: { headerShown: false },
|
options: { headerShown: false },
|
||||||
|
|||||||
119
front/components/ScoreModal.tsx
Normal file
119
front/components/ScoreModal.tsx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
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';
|
||||||
|
import { StackActions } from '@react-navigation/native';
|
||||||
|
|
||||||
|
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 w="xl" 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">{Math.max(score, 0)}%</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.entries(column).map(([key, [value, color]]) => {
|
||||||
|
const translationKey = key as TranslationKey;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row
|
||||||
|
key={translationKey}
|
||||||
|
style={{ justifyContent: 'space-between' }}
|
||||||
|
>
|
||||||
|
<Translate
|
||||||
|
translationKey={translationKey}
|
||||||
|
fontWeight={'bold'}
|
||||||
|
color={`${color}.500`}
|
||||||
|
/>
|
||||||
|
<Text>x{value}</Text>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Column>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
<Row w="100%" style={{ justifyContent: 'space-between' }}>
|
||||||
|
<ButtonBase
|
||||||
|
style={{}}
|
||||||
|
icon={Play}
|
||||||
|
type="outlined"
|
||||||
|
title={translate('playAgain')}
|
||||||
|
onPress={() =>
|
||||||
|
navigation.dispatch(StackActions.replace('Play', { songId: props.songId }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ButtonBase
|
||||||
|
style={{}}
|
||||||
|
icon={Play}
|
||||||
|
type="filled"
|
||||||
|
title={translate('menuMusic')}
|
||||||
|
onPress={() =>
|
||||||
|
navigation.canGoBack()
|
||||||
|
? navigation.goBack()
|
||||||
|
: navigation.navigate('HomeNew', {})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScoreModal;
|
||||||
@@ -8,7 +8,7 @@ import React from 'react';
|
|||||||
import GlassmorphismCC from './Glassmorphism';
|
import GlassmorphismCC from './Glassmorphism';
|
||||||
|
|
||||||
type PopupCCProps = {
|
type PopupCCProps = {
|
||||||
title: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
@@ -53,17 +53,19 @@ const PopupCC = ({ title, description, children, isVisible, setIsVisible }: Popu
|
|||||||
onPress={async () => setIsVisible(false)}
|
onPress={async () => setIsVisible(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Heading
|
{title !== undefined && (
|
||||||
size="md"
|
<Heading
|
||||||
style={{
|
size="md"
|
||||||
display: 'flex',
|
style={{
|
||||||
flexDirection: 'row',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
alignItems: 'center',
|
||||||
}}
|
justifyContent: 'space-between',
|
||||||
>
|
}}
|
||||||
<Text style={{ flex: 1 }}>{title}</Text>
|
>
|
||||||
</Heading>
|
<Text style={{ flex: 1 }}>{title}</Text>
|
||||||
|
</Heading>
|
||||||
|
)}
|
||||||
{description !== undefined && <Text>{description}</Text>}
|
{description !== undefined && <Text>{description}</Text>}
|
||||||
{children !== undefined && children}
|
{children !== undefined && children}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Cup, Discover, Music, SearchNormal1, Setting2, User } from 'iconsax-rea
|
|||||||
import { LoadingView } from '../Loading';
|
import { LoadingView } from '../Loading';
|
||||||
import ScaffoldDesktopCC from './ScaffoldDesktopCC';
|
import ScaffoldDesktopCC from './ScaffoldDesktopCC';
|
||||||
import ScaffoldMobileCC from './ScaffoldMobileCC';
|
import ScaffoldMobileCC from './ScaffoldMobileCC';
|
||||||
|
import { useAssets } from 'expo-asset';
|
||||||
|
|
||||||
const menu = [
|
const menu = [
|
||||||
{ type: 'main', title: 'menuDiscovery', icon: Discover, link: 'HomeNew' },
|
{ type: 'main', title: 'menuDiscovery', icon: Discover, link: 'HomeNew' },
|
||||||
@@ -37,10 +38,11 @@ const ScaffoldCC = ({
|
|||||||
return <LoadingView />;
|
return <LoadingView />;
|
||||||
}
|
}
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
const logo =
|
const [logo] = useAssets(
|
||||||
colorScheme == 'light'
|
colorScheme == 'light'
|
||||||
? require('../../assets/icon_light.png')
|
? require('../../assets/icon_light.png')
|
||||||
: require('../../assets/icon_dark.png');
|
: require('../../assets/icon_dark.png')
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex style={{ flex: 1, backgroundColor: '#cdd4fd' }}>
|
<Flex style={{ flex: 1, backgroundColor: '#cdd4fd' }}>
|
||||||
@@ -48,7 +50,7 @@ const ScaffoldCC = ({
|
|||||||
<ScaffoldMobileCC
|
<ScaffoldMobileCC
|
||||||
enableScroll={enableScroll}
|
enableScroll={enableScroll}
|
||||||
user={userQuery.data}
|
user={userQuery.data}
|
||||||
logo={logo}
|
logo={logo?.at(0)?.uri ?? ''}
|
||||||
routeName={routeName}
|
routeName={routeName}
|
||||||
menu={menu}
|
menu={menu}
|
||||||
widthPadding={withPadding}
|
widthPadding={withPadding}
|
||||||
@@ -58,7 +60,7 @@ const ScaffoldCC = ({
|
|||||||
) : (
|
) : (
|
||||||
<ScaffoldDesktopCC
|
<ScaffoldDesktopCC
|
||||||
user={userQuery.data}
|
user={userQuery.data}
|
||||||
logo={logo}
|
logo={logo?.at(0)?.uri ?? ''}
|
||||||
routeName={routeName}
|
routeName={routeName}
|
||||||
menu={menu}
|
menu={menu}
|
||||||
widthPadding={withPadding}
|
widthPadding={withPadding}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable no-mixed-spaces-and-tabs */
|
/* eslint-disable no-mixed-spaces-and-tabs */
|
||||||
import { View, Image, TouchableOpacity } from 'react-native';
|
import { View, Image, Pressable } from 'react-native';
|
||||||
import { Divider, Text, ScrollView, Row, useMediaQuery, useTheme } from 'native-base';
|
import { Divider, Text, ScrollView, Row, useMediaQuery, useTheme } from 'native-base';
|
||||||
import { useQuery } from '../../Queries';
|
import { useQuery } from '../../Queries';
|
||||||
import API from '../../API';
|
import API from '../../API';
|
||||||
@@ -36,7 +36,21 @@ const SongHistory = (props: { quantity: number }) => {
|
|||||||
return <LoadingView />;
|
return <LoadingView />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const musics = history.data.map((h) => h.song)?.slice(0, props.quantity);
|
const musics = history.data
|
||||||
|
.reduce(
|
||||||
|
(acc, curr) => {
|
||||||
|
if (acc.length === 0) {
|
||||||
|
return [curr];
|
||||||
|
}
|
||||||
|
if (acc.find((h) => h.song!.id === curr.song!.id)) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
return [...acc, curr];
|
||||||
|
},
|
||||||
|
[] as typeof history.data
|
||||||
|
)
|
||||||
|
.map((h) => h.song)
|
||||||
|
?.slice(0, props.quantity);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
@@ -45,18 +59,18 @@ const SongHistory = (props: { quantity: number }) => {
|
|||||||
) : (
|
) : (
|
||||||
musics.map((song) => (
|
musics.map((song) => (
|
||||||
<View
|
<View
|
||||||
key={'short-history-tab' + song.id}
|
key={'short-history-tab' + song!.id}
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TouchableOpacity
|
<Pressable
|
||||||
onPress={() => navigation.navigate('Play', { songId: song.id })}
|
onPress={() => navigation.navigate('Play', { songId: song!.id })}
|
||||||
>
|
>
|
||||||
<Text numberOfLines={1}>{song.name}</Text>
|
<Text numberOfLines={1}>{song!.name}</Text>
|
||||||
</TouchableOpacity>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,15 +1,32 @@
|
|||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
import ResponseHandler from './ResponseHandler';
|
import ResponseHandler from './ResponseHandler';
|
||||||
|
import { ModelValidator } from './Model';
|
||||||
import { SongValidator } from './Song';
|
import { SongValidator } from './Song';
|
||||||
|
|
||||||
export const SongHistoryItemValidator = yup.object({
|
export const SongHistoryItemValidator = yup
|
||||||
songID: yup.number().required(),
|
.object({
|
||||||
song: SongValidator.required(),
|
songID: yup.number().required(),
|
||||||
userID: yup.number().required(),
|
song: SongValidator.optional(),
|
||||||
score: yup.number().required(),
|
userID: yup.number().required(),
|
||||||
playDate: yup.date().required(),
|
info: yup
|
||||||
difficulties: yup.mixed().required(),
|
.object({
|
||||||
});
|
good: yup.number().required(),
|
||||||
|
great: yup.number().required(),
|
||||||
|
score: yup.number().required(),
|
||||||
|
wrong: yup.number().required(),
|
||||||
|
missed: yup.number().required(),
|
||||||
|
perfect: yup.number().required(),
|
||||||
|
max_score: yup.number().required(),
|
||||||
|
max_streak: yup.number().required(),
|
||||||
|
// there's also a current streak key but it doesn't
|
||||||
|
// conceptually makes sense outside of the played game
|
||||||
|
})
|
||||||
|
.required(),
|
||||||
|
score: yup.number().required(),
|
||||||
|
playDate: yup.date().required(),
|
||||||
|
difficulties: yup.mixed().required(),
|
||||||
|
})
|
||||||
|
.concat(ModelValidator);
|
||||||
export type SongHistoryItem = yup.InferType<typeof SongHistoryItemValidator>;
|
export type SongHistoryItem = yup.InferType<typeof SongHistoryItemValidator>;
|
||||||
|
|
||||||
export const SongHistoryItemHandler: ResponseHandler<SongHistoryItem> = {
|
export const SongHistoryItemHandler: ResponseHandler<SongHistoryItem> = {
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ const HomeView = (props: RouteProps<{}>) => {
|
|||||||
playHistoryQuery.data
|
playHistoryQuery.data
|
||||||
?.map((x) => x.song)
|
?.map((x) => x.song)
|
||||||
.map((song) => ({
|
.map((song) => ({
|
||||||
cover: song.cover,
|
cover: song!.cover,
|
||||||
name: song.name,
|
name: song!.name,
|
||||||
songId: song.id,
|
songId: song!.id,
|
||||||
artistName: song.artist!.name,
|
artistName: song!.artist!.name,
|
||||||
})) ?? []
|
})) ?? []
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import PopupCC from '../components/UI/PopupCC';
|
|||||||
import ButtonBase from '../components/UI/ButtonBase';
|
import ButtonBase from '../components/UI/ButtonBase';
|
||||||
import { Clock, Cup } from 'iconsax-react-native';
|
import { Clock, Cup } from 'iconsax-react-native';
|
||||||
import PlayViewControlBar from '../components/Play/PlayViewControlBar';
|
import PlayViewControlBar from '../components/Play/PlayViewControlBar';
|
||||||
|
import ScoreModal from '../components/ScoreModal';
|
||||||
|
|
||||||
type PlayViewProps = {
|
type PlayViewProps = {
|
||||||
songId: number;
|
songId: number;
|
||||||
@@ -80,10 +81,12 @@ const PlayView = ({ songId, route }: RouteProps<PlayViewProps>) => {
|
|||||||
const [paused, setPause] = useState<boolean>(true);
|
const [paused, setPause] = useState<boolean>(true);
|
||||||
const stopwatch = useStopwatch();
|
const stopwatch = useStopwatch();
|
||||||
const [time, setTime] = useState(0);
|
const [time, setTime] = useState(0);
|
||||||
|
const [endResult, setEndResult] = useState<unknown>();
|
||||||
const songHistory = useQuery(API.getSongHistory(songId));
|
const songHistory = useQuery(API.getSongHistory(songId));
|
||||||
const [score, setScore] = useState(0); // Between 0 and 100
|
const [score, setScore] = useState(0); // Between 0 and 100
|
||||||
// const fadeAnim = useRef(new Animated.Value(0)).current;
|
// const fadeAnim = useRef(new Animated.Value(0)).current;
|
||||||
const getElapsedTime = () => stopwatch.getElapsedRunningTime() - 3000;
|
const getElapsedTime = () => stopwatch.getElapsedRunningTime() - 3000;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [midiKeyboardFound, setMidiKeyboardFound] = useState<boolean>();
|
const [midiKeyboardFound, setMidiKeyboardFound] = useState<boolean>();
|
||||||
// first number is the note, the other is the time when pressed on release the key is removed
|
// first number is the note, the other is the time when pressed on release the key is removed
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
@@ -183,11 +186,10 @@ const PlayView = ({ songId, route }: RouteProps<PlayViewProps>) => {
|
|||||||
if (data.type == 'end') {
|
if (data.type == 'end') {
|
||||||
endMsgReceived = true;
|
endMsgReceived = true;
|
||||||
webSocket.current?.close();
|
webSocket.current?.close();
|
||||||
navigation.dispatch(
|
setEndResult({ songId: song.data!.id, ...data });
|
||||||
StackActions.replace('Score', { songId: song.data!.id, ...data })
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
const points = data.info.score;
|
const points = data.info.score;
|
||||||
const maxPoints = data.info.max_score || 1;
|
const maxPoints = data.info.max_score || 1;
|
||||||
@@ -319,16 +321,52 @@ const PlayView = ({ songId, route }: RouteProps<PlayViewProps>) => {
|
|||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<PopupCC isVisible={endResult != undefined}>
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(() => (endResult ? <ScoreModal {...(endResult as any)} /> : <></>))()
|
||||||
|
}
|
||||||
|
</PopupCC>
|
||||||
|
<PopupCC
|
||||||
|
title={translate('selectPlayMode')}
|
||||||
|
description={translate('selectPlayModeExplaination')}
|
||||||
|
isVisible={playType == null}
|
||||||
|
setIsVisible={
|
||||||
|
navigation.canGoBack()
|
||||||
|
? (isVisible) => {
|
||||||
|
if (!isVisible) {
|
||||||
|
// If we dismiss the popup, Go to previous page
|
||||||
|
navigation.goBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Row style={{ justifyContent: 'space-between' }}>
|
||||||
|
<ButtonBase
|
||||||
|
style={{}}
|
||||||
|
type="outlined"
|
||||||
|
title={translate('practiceBtn')}
|
||||||
|
onPress={async () => setPlayType('practice')}
|
||||||
|
/>
|
||||||
|
<ButtonBase
|
||||||
|
style={{}}
|
||||||
|
type="filled"
|
||||||
|
title={translate('playBtn')}
|
||||||
|
onPress={async () => setPlayType('normal')}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</PopupCC>
|
||||||
{(
|
{(
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
'lastScore',
|
'lastScore',
|
||||||
songHistory.data?.best ?? 0,
|
songHistory.data?.history.at(0)?.score ?? 0,
|
||||||
() => <Clock color={statColor} />,
|
() => <Clock color={statColor} />,
|
||||||
] as const,
|
] as const,
|
||||||
[
|
[
|
||||||
'bestScore',
|
'bestScore',
|
||||||
songHistory.data?.history.at(0)?.score ?? 0,
|
songHistory.data?.best ?? 0,
|
||||||
() => <Cup color={statColor} />,
|
() => <Cup color={statColor} />,
|
||||||
],
|
],
|
||||||
] as const
|
] as const
|
||||||
@@ -435,7 +473,6 @@ const PlayView = ({ songId, route }: RouteProps<PlayViewProps>) => {
|
|||||||
score={score}
|
score={score}
|
||||||
time={time}
|
time={time}
|
||||||
paused={paused}
|
paused={paused}
|
||||||
disabled={!midiKeyboardFound}
|
|
||||||
song={song.data}
|
song={song.data}
|
||||||
onEnd={onEnd}
|
onEnd={onEnd}
|
||||||
onPause={onPause}
|
onPause={onPause}
|
||||||
|
|||||||
@@ -1,160 +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 { 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, ['artist']));
|
|
||||||
const recommendations = useQuery(API.getSongSuggestions(['artist']));
|
|
||||||
|
|
||||||
if (!recommendations.data || !songQuery.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>{songQuery.data.artist!.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: i.artist!.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;
|
|
||||||
@@ -10,4 +10,4 @@ COPY ./requirements.txt .
|
|||||||
RUN pip3 install -r ./requirements.txt
|
RUN pip3 install -r ./requirements.txt
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
CMD /usr/bin/websocketd --port=6543 --staticdir=. -- python3 main.py
|
CMD /usr/bin/websocketd --port=6543 --staticdir=. --passenv API_KEY_SCORO,BACK_URL,MUSICS_FOLDER -- python3 main.py
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ WORKDIR /app
|
|||||||
COPY ./requirements.txt .
|
COPY ./requirements.txt .
|
||||||
RUN pip install -r ./requirements.txt
|
RUN pip install -r ./requirements.txt
|
||||||
|
|
||||||
CMD /usr/bin/websocketd --port=6543 --devconsole -- python3 ./main.py
|
CMD /usr/bin/websocketd --port=6543 --devconsole --passenv API_KEY_SCORO,BACK_URL,MUSICS_FOLDER -- python3 ./main.py
|
||||||
|
|||||||
Reference in New Issue
Block a user