Merge pull request #123 from Chroma-Case/front/play-page
Front: play page
This commit is contained in:
@@ -175,7 +175,9 @@ jobs:
|
||||
context: ./front
|
||||
push: true
|
||||
tags: ${{steps.meta_front.outputs.tags}}
|
||||
build-args: API_URL=${{secrets.API_URL}}
|
||||
build-args: |
|
||||
API_URL=${{secrets.API_URL}}
|
||||
SCORO_URL=${{secrets.SCORO_URL}}
|
||||
- name: Docker meta scorometer
|
||||
id: meta_scorometer
|
||||
uses: docker/metadata-action@v4
|
||||
|
||||
+2
-1
@@ -35,7 +35,8 @@ services:
|
||||
build:
|
||||
context: ./front
|
||||
args:
|
||||
- API_URL=${API_URL}
|
||||
- API_URL=${API_URL}
|
||||
- SCORO_URL=${SCORO_URL}
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
|
||||
+2
-1
@@ -34,6 +34,7 @@ export default class API {
|
||||
method: params.method ?? 'GET'
|
||||
});
|
||||
const body = await response.text();
|
||||
|
||||
try {
|
||||
const jsonResponse = body.length != 0 ? JSON.parse(body) : {};
|
||||
if (!response.ok) {
|
||||
@@ -196,7 +197,7 @@ export default class API {
|
||||
*/
|
||||
public static async getUserRecommendations(): Promise<Song[]> {
|
||||
return Array.of(4).map((i) => ({
|
||||
id: i,
|
||||
id: 1,
|
||||
name: `Recommended Song ${i}`,
|
||||
artistId: i,
|
||||
genreId: i,
|
||||
|
||||
@@ -12,6 +12,8 @@ RUN yarn global add sharp-cli@^2.1.0
|
||||
COPY . .
|
||||
ARG API_URL
|
||||
ENV API_URL=$API_URL
|
||||
ARG SCORO_URL
|
||||
ENV SCORO_URL=$SCORO_URL
|
||||
|
||||
RUN expo build:web
|
||||
|
||||
|
||||
+22
-13
@@ -1,16 +1,19 @@
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import React from 'react';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { useSelector } from './state/Store';
|
||||
import { RootState, useSelector } from './state/Store';
|
||||
import { translate } from './i18n/i18n';
|
||||
import SongLobbyView from './views/SongLobbyView';
|
||||
import AuthenticationView from './views/AuthenticationView';
|
||||
import HomeView from './views/HomeView';
|
||||
import SearchView from './views/SearchView';
|
||||
import PartitionView from './views/PartitionView';
|
||||
import SetttingsNavigator from './views/SettingsView';
|
||||
import { useQuery } from 'react-query';
|
||||
import API from './API';
|
||||
import PlayView from './views/PlayView';
|
||||
import ScoreView from './views/ScoreView';
|
||||
import { Center } from 'native-base';
|
||||
import LoadingComponent from './components/Loading';
|
||||
import ProfileView from './views/ProfileView';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
@@ -19,9 +22,10 @@ export const protectedRoutes = <>
|
||||
<Stack.Screen name="Home" component={HomeView} options={{ title: translate('welcome') }} />
|
||||
<Stack.Screen name="Settings" component={SetttingsNavigator} options={{ title: 'Settings' }} />
|
||||
<Stack.Screen name="Song" component={SongLobbyView} options={{ title: translate('play') }} />
|
||||
<Stack.Screen name="Play" component={() => PlayView({ songId: 1 })} options={{ title: translate('play') }} />
|
||||
<Stack.Screen name="Score" component={ScoreView} options={{ title: translate('score') }} />
|
||||
<Stack.Screen name="Search" component={SearchView} options={{ title: translate('search') }} />
|
||||
<Stack.Screen name="User" component={ProfileView} options={{ title: translate('user') }} />
|
||||
<Stack.Screen name="Partition" component={PartitionView} options={{ title: translate('partition') }} />
|
||||
</>;
|
||||
|
||||
export const publicRoutes = <React.Fragment>
|
||||
@@ -29,20 +33,25 @@ export const publicRoutes = <React.Fragment>
|
||||
</React.Fragment>;
|
||||
|
||||
export const Router = () => {
|
||||
const isAuthentified = useSelector((state) => state.user.accessToken !== undefined);
|
||||
const userProfile = useQuery(['user', 'me'], () => API.getUserInfo(), {
|
||||
enabled: isAuthentified
|
||||
const accessToken = useSelector((state: RootState) => state.user.accessToken);
|
||||
const userProfile = useQuery(['user', 'me', accessToken], () => API.getUserInfo(), {
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
if (userProfile.isLoading && !userProfile.data) {
|
||||
return <Center style={{ flexGrow: 1 }}>
|
||||
<LoadingComponent/>
|
||||
</Center>
|
||||
}
|
||||
return (
|
||||
<NavigationContainer>
|
||||
{isAuthentified && !userProfile.isError
|
||||
? <Stack.Navigator>
|
||||
{protectedRoutes}
|
||||
</Stack.Navigator>
|
||||
: <Stack.Navigator>
|
||||
{publicRoutes}
|
||||
</Stack.Navigator>
|
||||
<Stack.Navigator>
|
||||
{ userProfile.isSuccess && accessToken
|
||||
? protectedRoutes
|
||||
: publicRoutes
|
||||
}
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ module.exports = {
|
||||
},
|
||||
"extra": {
|
||||
apiUrl: process.env.API_URL,
|
||||
scoroUrl: process.env.SCORO_URL,
|
||||
"eas": {
|
||||
"projectId": "dade8e5e-3e2c-49f7-98c5-cf8834c7ebb2"
|
||||
}
|
||||
|
||||
@@ -10,9 +10,7 @@ type PartitionVisualizerProps = {
|
||||
};
|
||||
|
||||
const PartitionVisualizer = ({ songId }: PartitionVisualizerProps) => {
|
||||
const partitionRessources = useQuery(["partition"], () =>
|
||||
API.getPartitionRessources(songId)
|
||||
);
|
||||
|
||||
|
||||
if (!partitionRessources.data) {
|
||||
return (
|
||||
@@ -22,7 +20,7 @@ const PartitionVisualizer = ({ songId }: PartitionVisualizerProps) => {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<SlideView sources={partitionRessources.data} speed={200} startAt={0} />
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Text } from "native-base";
|
||||
import { translate } from "../i18n/i18n";
|
||||
import { en } from "../i18n/Translations";
|
||||
import { RootState, useSelector } from "../state/Store";
|
||||
import { Text } from "native-base";
|
||||
|
||||
type TranslateProps = {
|
||||
translationKey: keyof typeof en;
|
||||
|
||||
@@ -6,6 +6,7 @@ export const en = {
|
||||
changeLanguageBtn: "Change language",
|
||||
searchBtn: "Search",
|
||||
playBtn: 'Play',
|
||||
playAgain: 'Play Again',
|
||||
songPageBtn: 'Go to song page',
|
||||
level: 'Level',
|
||||
chapters: 'Chapters',
|
||||
@@ -22,8 +23,10 @@ export const en = {
|
||||
mySkillsToImprove: "My Competencies to work on",
|
||||
recentlyPlayed: 'Recently played',
|
||||
search: 'Search',
|
||||
songsToGetBetter: 'Recommendations',
|
||||
lastSearched: "Last searched",
|
||||
levelProgress: 'good notes',
|
||||
score: 'Score',
|
||||
|
||||
// profile page
|
||||
user: 'Profile',
|
||||
@@ -70,7 +73,10 @@ export const en = {
|
||||
invalidEmail: 'Invalid email',
|
||||
accountCreated: 'Account created',
|
||||
loggedIn: 'Logged in',
|
||||
precisionScore: "Precision",
|
||||
goodNotesInARow: 'Good notes in a row',
|
||||
usernameTaken: 'Username already taken',
|
||||
goodNotes: 'good notes',
|
||||
|
||||
// categories
|
||||
username: 'Username',
|
||||
@@ -93,6 +99,7 @@ export const fr: typeof en = {
|
||||
changeLanguageBtn: "Changer la langue",
|
||||
searchBtn: "Rechercher",
|
||||
playBtn: 'Jouer',
|
||||
playAgain: 'Rejouer',
|
||||
songPageBtn: 'Aller sur la page de la chanson',
|
||||
level: 'Niveau',
|
||||
chapters: 'Chapitres',
|
||||
@@ -164,6 +171,11 @@ export const fr: typeof en = {
|
||||
password: 'Mot de passe',
|
||||
email: "Email",
|
||||
repeatPassword: "Confirmer",
|
||||
score: 'Score',
|
||||
precisionScore: "Précision",
|
||||
goodNotesInARow: 'Bonnes notes à la suite',
|
||||
songsToGetBetter: 'Recommendations',
|
||||
goodNotes: 'bonnes notes',
|
||||
changepasswdBtn: 'Changer le mot de pass',
|
||||
changeemailBtn: 'Changer l\'email',
|
||||
googleacctBtn: 'Compte Google',
|
||||
@@ -177,9 +189,17 @@ export const sp: typeof en = {
|
||||
welcomeMessage: "Bienvenue",
|
||||
signoutBtn: 'Desconectarse',
|
||||
signinBtn: 'Connectarse',
|
||||
changepasswdBtn: 'Changer le mot de pass',
|
||||
changeemailBtn: 'Change l\'email',
|
||||
googleacctBtn: 'Compte Google',
|
||||
goodNotes: 'bonnes notes',
|
||||
|
||||
// competencies
|
||||
changeLanguageBtn: "Changer de langue",
|
||||
searchBtn: "Rechercher",
|
||||
playBtn: "Jouer",
|
||||
playAgain: 'Rejouer',
|
||||
precisionScore: "Précision",
|
||||
songPageBtn: "Chanson",
|
||||
level: "Niveau",
|
||||
chapters: "Chapitres",
|
||||
@@ -246,15 +266,15 @@ export const sp: typeof en = {
|
||||
accountCreated: 'Compte créé',
|
||||
loggedIn: 'Connectado',
|
||||
usernameTaken: 'Nombre de usuario ya tomado',
|
||||
score: 'Score',
|
||||
goodNotesInARow: 'Bonnes notes à la suite',
|
||||
songsToGetBetter: 'Recommendations',
|
||||
|
||||
// categories
|
||||
username: 'Nom d\'utilisateur',
|
||||
password: 'Mot de passe',
|
||||
email: 'Email',
|
||||
repeatPassword: 'Répéter le mot de passe',
|
||||
changepasswdBtn: 'Changer le mot de pass',
|
||||
changeemailBtn: 'Change l\'email',
|
||||
googleacctBtn: 'Compte Google',
|
||||
|
||||
partition: 'Partition',
|
||||
};
|
||||
+17
-14
@@ -17,6 +17,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^13.0.0",
|
||||
"@expo/webpack-config": "^0.17.3",
|
||||
"@react-native-async-storage/async-storage": "^1.17.11",
|
||||
"@react-navigation/native": "^6.0.11",
|
||||
"@react-navigation/native-stack": "^6.7.0",
|
||||
@@ -26,32 +27,34 @@
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-query": "^1.2.9",
|
||||
"@types/react-test-renderer": "^18.0.0",
|
||||
"expo": "~45.0.0",
|
||||
"expo-asset": "~8.5.0",
|
||||
"expo-dev-client": "~1.0.0",
|
||||
"expo-secure-store": "~11.2.0",
|
||||
"expo-splash-screen": "~0.15.1",
|
||||
"expo-status-bar": "~1.3.0",
|
||||
"expo-updates": "~0.13.4",
|
||||
"expo": "^47.0.8",
|
||||
"expo-asset": "~8.7.0",
|
||||
"expo-dev-client": "~2.0.1",
|
||||
"expo-screen-orientation": "^5.0.1",
|
||||
"expo-secure-store": "~12.0.0",
|
||||
"expo-splash-screen": "~0.17.5",
|
||||
"expo-status-bar": "~1.4.2",
|
||||
"format-duration": "^2.0.0",
|
||||
"i18next": "^21.8.16",
|
||||
"install": "^0.13.0",
|
||||
"jest": "^26.6.3",
|
||||
"jest-expo": "^45.0.1",
|
||||
"moti": "^0.22.0",
|
||||
"native-base": "^3.4.17",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react": "18.1.0",
|
||||
"react-dom": "18.1.0",
|
||||
"react-i18next": "^11.18.3",
|
||||
"react-native": "0.68.2",
|
||||
"react-native": "0.70.5",
|
||||
"react-native-paper": "^4.12.5",
|
||||
"react-native-safe-area-context": "4.4.1",
|
||||
"react-native-screens": "~3.18.0",
|
||||
"react-native-reanimated": "~2.8.0",
|
||||
"react-native-safe-area-context": "4.2.4",
|
||||
"react-native-screens": "~3.11.1",
|
||||
"react-native-super-grid": "^4.6.1",
|
||||
"react-native-svg": "12.3.0",
|
||||
"react-native-svg": "13.4.0",
|
||||
"react-native-testing-library": "^6.0.0",
|
||||
"react-native-web": "0.17.7",
|
||||
"react-native-web": "~0.18.9",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-timer-hook": "^3.0.5",
|
||||
"redux-persist": "^6.0.0",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
|
||||
@@ -102,9 +102,6 @@ const HomeView = () => {
|
||||
<Button backgroundColor={theme.colors.primary[600]} rounded={"full"} size="sm" onPress={() => navigation.navigate('Settings')} >
|
||||
<Translate translationKey='settingsBtn'/>
|
||||
</Button>
|
||||
<Button backgroundColor={theme.colors.primary[600]} rounded={"full"} size="sm" onPress={() => navigation.navigate('Partition')} >
|
||||
<Translate translationKey='partition'/>
|
||||
</Button>
|
||||
</Box>
|
||||
</VStack>
|
||||
</Box>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from "react";
|
||||
import { Box } from "native-base";
|
||||
import API from "../API";
|
||||
import PartitionVisualizer from "../components/PartitionVisualizer/PartitionVisualizer";
|
||||
|
||||
const PartitionView = () => {
|
||||
|
||||
return (
|
||||
<Box style={{ padding: 10 }}>
|
||||
<PartitionVisualizer songId={1} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PartitionView;
|
||||
@@ -0,0 +1,165 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { SafeAreaView, Text } from 'react-native';
|
||||
import * as ScreenOrientation from 'expo-screen-orientation';
|
||||
import { Box, Center, Column, IconButton, Progress, Row, View, useToast } from 'native-base';
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useQuery } from 'react-query';
|
||||
import API from '../API';
|
||||
import LoadingComponent from '../components/Loading';
|
||||
import Constants from 'expo-constants';
|
||||
import { useStopwatch } from 'react-timer-hook';
|
||||
import PartitionVisualizer from '../components/PartitionVisualizer/PartitionVisualizer';
|
||||
import SlideView from '../components/PartitionVisualizer/SlideView';
|
||||
|
||||
type PlayViewProps = {
|
||||
songId: number
|
||||
}
|
||||
|
||||
const PlayView = ({ songId }: PlayViewProps) => {
|
||||
const navigation = useNavigation();
|
||||
const song = useQuery(['song'], () => API.getSong(songId));
|
||||
const toast = useToast();
|
||||
const webSocket = useRef<WebSocket>();
|
||||
const timer = useStopwatch({ autoStart: false });
|
||||
const [paused, setPause] = useState<boolean>();
|
||||
const partitionRessources = useQuery(["partition"], () =>
|
||||
API.getPartitionRessources(songId)
|
||||
);
|
||||
|
||||
const onPause = () => {
|
||||
timer.pause();
|
||||
setPause(true);
|
||||
webSocket.current?.send(JSON.stringify({
|
||||
type: "pause",
|
||||
paused: true,
|
||||
time: Date.now()
|
||||
}));
|
||||
}
|
||||
const onResume = () => {
|
||||
setPause(false);
|
||||
timer.start();
|
||||
webSocket.current?.send(JSON.stringify({
|
||||
type: "pause",
|
||||
paused: false,
|
||||
time: Date.now()
|
||||
}));
|
||||
}
|
||||
const onEnd = () => {
|
||||
webSocket.current?.close();
|
||||
}
|
||||
|
||||
const onMIDISuccess = (access) => {
|
||||
const inputs = access.inputs;
|
||||
webSocket.current?.send(JSON.stringify({
|
||||
type: "start",
|
||||
paused: false,
|
||||
time: Date.now()
|
||||
}));
|
||||
|
||||
if (inputs.size < 2) {
|
||||
toast.show({ description: 'No MIDI Keyboard found' });
|
||||
return;
|
||||
}
|
||||
toast.show({ description: `MIDI ready!`, placement: 'top' });
|
||||
let inputIndex = 0;
|
||||
webSocket.current = new WebSocket(Constants.manifest?.extra?.scoroUrl);
|
||||
webSocket.current.onopen = () => {
|
||||
webSocket.current!.send(JSON.stringify({
|
||||
type: "start",
|
||||
name: "clair-de-lune" /*song.data.id*/,
|
||||
}));
|
||||
timer.start();
|
||||
};
|
||||
webSocket.current.onmessage = (message) => {
|
||||
try {
|
||||
const data = JSON.parse(message.data);
|
||||
if (data.type == 'end') {
|
||||
navigation.navigate('Score');
|
||||
} else if (data.song_launched == undefined) {
|
||||
toast.show({ description: data, placement: 'top', colorScheme: 'secondary' });
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
setPause(false);
|
||||
inputs.forEach((input) => {
|
||||
if (inputIndex != 0) {
|
||||
return;
|
||||
}
|
||||
input.onmidimessage = (message) => {
|
||||
const keyIsPressed = message.data[2] == 100;
|
||||
const keyCode = message.data[1];
|
||||
webSocket.current?.send(JSON.stringify({
|
||||
type: keyIsPressed ? "note_on" : "note_off",
|
||||
node: keyCode,
|
||||
intensity: null,
|
||||
time: Date.now()
|
||||
}))
|
||||
}
|
||||
inputIndex++;
|
||||
});
|
||||
}
|
||||
const onMIDIFailure = () => {
|
||||
toast.show({ description: `Failed to get MIDI access` });
|
||||
}
|
||||
useEffect(() => {
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE).catch(() => {});
|
||||
navigator.requestMIDIAccess().then(onMIDISuccess, onMIDIFailure);
|
||||
|
||||
return () => {
|
||||
ScreenOrientation.unlockAsync().catch(() => {});
|
||||
clearInterval(timer);
|
||||
onEnd();
|
||||
}
|
||||
}, [])
|
||||
const score = 20;
|
||||
|
||||
if (!song.data || !partitionRessources.data) {
|
||||
return <Center style={{ flexGrow: 1 }}>
|
||||
<LoadingComponent/>
|
||||
</Center>
|
||||
}
|
||||
return (
|
||||
<SafeAreaView style={{ flexGrow: 1, flexDirection: 'column' }}>
|
||||
<View style={{ flexGrow: 1 }}>
|
||||
<SlideView sources={partitionRessources.data} speed={200} startAt={0} />
|
||||
</View>
|
||||
<Box shadow={4} style={{ height: '12%', width:'100%', borderWidth: 0.5, margin: 5 }}>
|
||||
<Row justifyContent='space-between' style={{ flexGrow: 1, alignItems: 'center' }} >
|
||||
<Column space={2} style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text style={{ fontWeight: 'bold' }}>Score: {score}%</Text>
|
||||
<Progress value={score} style={{ width: '90%' }}/>
|
||||
</Column>
|
||||
<Center style={{ flex: 1, alignItems: 'center' }}>
|
||||
<Text style={{ fontWeight: '700' }}>Rolling in the Deep</Text>
|
||||
</Center>
|
||||
<Row style={{ flex: 1, height: '100%', justifyContent: 'space-evenly', alignItems: 'center' }}>
|
||||
<IconButton size='sm' colorScheme='secondary' variant='solid' _icon={{
|
||||
as: Ionicons,
|
||||
name: "play-back"
|
||||
}}/>
|
||||
<IconButton size='sm' variant='solid' _icon={{
|
||||
as: Ionicons,
|
||||
name: paused === false ? "pause" : "play"
|
||||
}} onPress={() => {
|
||||
if (paused == true) {
|
||||
onResume();
|
||||
} else if (paused === false) {
|
||||
onPause();
|
||||
}
|
||||
}}/>
|
||||
<Text>{timer.minutes}:{timer.seconds.toString().padStart(2, '0')}</Text>
|
||||
<IconButton size='sm' colorScheme='coolGray' variant='solid' _icon={{
|
||||
as: Ionicons,
|
||||
name: "stop"
|
||||
}} onPress={() => navigation.navigate('Score')}/>
|
||||
</Row>
|
||||
</Row>
|
||||
</Box>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
export default PlayView
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Box, Button, Card, Column, Image, Progress, Row, Text, View, useTheme } from "native-base"
|
||||
import Translate from "../components/Translate";
|
||||
import SongCardGrid from "../components/SongCardGrid";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { CardBorderRadius } from "../components/Card";
|
||||
|
||||
const ScoreView = (/*{ songId }, { songId: number }*/) => {
|
||||
const theme = useTheme();
|
||||
const navigation = useNavigation();
|
||||
// const songQuery = useQuery(['song', props.songId], () => API.getSong(props.songId));
|
||||
// const songScoreQuery = useQuery(['song', props.songId, 'score', 'latest'], () => API.getLastSongPerformanceScore(props.songId));
|
||||
// const perfoamnceRecommandationsQuery = useQuery(['song', props.songId, 'score', 'latest', 'recommendations'], () => API.getLastSongPerformanceScore(props.songId));
|
||||
return <Column style={{ flexGrow: 1, justifyContent: 'space-evenly', alignItems: 'center', padding: 10 }}>
|
||||
<Text bold fontSize='lg'>Rolling in the Deep</Text>
|
||||
<Text bold>Adele - 3:45</Text>
|
||||
<Row style={{ flexGrow: 0.5, justifyContent: 'center' }}>
|
||||
<Card shadow={3} style={{ aspectRatio: 1 }}>
|
||||
<Image
|
||||
style={{ zIndex: 0, aspectRatio: 1, margin: 5, borderRadius: CardBorderRadius}}
|
||||
source={{ uri: 'https://imgs.search.brave.com/AinqAz0knOSOt0V3rcv7ps7aMVCo0QQfZ-1NTdwVjK0/rs:fit:1200:1200:1/g:ce/aHR0cDovLzEuYnAu/YmxvZ3Nwb3QuY29t/Ly0xTmZtZTdKbDVk/US9UaHd0Y3pieEVa/SS9BQUFBQUFBQUFP/TS9QdGx6ZWtWd2Zt/ay9zMTYwMC9BZGVs/ZSstKzIxKyUyNTI4/T2ZmaWNpYWwrQWxi/dW0rQ292ZXIlMjUy/OS5qcGc' }}
|
||||
/>
|
||||
</Card>
|
||||
<Card shadow={3} style={{ aspectRatio: 1 }}>
|
||||
<Column style={{ justifyContent: 'space-evenly', flexGrow: 1 }}>
|
||||
<Row style={{ alignItems: 'center' }}>
|
||||
<Text bold fontSize='xl'>
|
||||
80
|
||||
</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='precisionScore' format={(t) => t + ' : '}/>
|
||||
<Text bold fontSize='xl'>
|
||||
{"80" + "%"}
|
||||
</Text>
|
||||
</Row>
|
||||
</Column>
|
||||
{/* Precision */}
|
||||
</Card>
|
||||
</Row>
|
||||
<SongCardGrid
|
||||
heading={<Text fontSize='sm'>
|
||||
<Translate translationKey="songsToGetBetter"/>
|
||||
</Text>}
|
||||
maxItemPerRow={5}
|
||||
songs={Array.of(1, 2, 3, 4, 5).map((i) => ({
|
||||
albumCover: "",
|
||||
songTitle: 'Song ' + i,
|
||||
artistName: "Artist",
|
||||
songId: i
|
||||
}))}
|
||||
/>
|
||||
<Row space={3} style={{ width: '100%', justifyContent: 'center' }}>
|
||||
<Button backgroundColor='gray.300' onPress={() => navigation.navigate('Home')}>
|
||||
<Translate translationKey='backBtn'/>
|
||||
</Button>
|
||||
<Button onPress={() => navigation.navigate('Song', { songId: 1 })}>
|
||||
<Translate translationKey='playAgain'/>
|
||||
</Button>
|
||||
</Row>
|
||||
</Column>
|
||||
}
|
||||
|
||||
export default ScoreView;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useRoute } from "@react-navigation/native";
|
||||
import { useNavigation, useRoute } from "@react-navigation/native";
|
||||
import { Button, Divider, Box, Center, Image, Text, VStack, PresenceTransition, Icon } from "native-base";
|
||||
import { useQuery } from 'react-query';
|
||||
import LoadingComponent from "../components/Loading";
|
||||
@@ -15,6 +15,7 @@ interface SongLobbyProps {
|
||||
|
||||
const SongLobbyView = () => {
|
||||
const route = useRoute();
|
||||
const navigation = useNavigation();
|
||||
const props: SongLobbyProps = route.params as any;
|
||||
const songQuery = useQuery(['song', props.songId], () => API.getSong(props.songId));
|
||||
const chaptersQuery = useQuery(['song', props.songId, 'chapters'], () => API.getSongChapters(props.songId));
|
||||
@@ -41,10 +42,10 @@ const SongLobbyView = () => {
|
||||
<Text bold fontSize='lg'>{songQuery.data!.title}</Text>
|
||||
<Text>
|
||||
<Translate translationKey='level'
|
||||
format={(level) => `3:20 - ${level} - ${ chaptersQuery.data!.reduce((a, b) => a + b.difficulty, 0) / chaptersQuery.data!.length }`}
|
||||
format={(level) => `${level} - ${ chaptersQuery.data!.reduce((a, b) => a + b.difficulty, 0) / chaptersQuery.data!.length }`}
|
||||
/>
|
||||
</Text>
|
||||
<Button width='auto' rightIcon={<Icon as={Ionicons} name="play-outline"/>}>
|
||||
<Button width='auto' onPress={() => navigation.navigate('Play', { songId: songQuery.data?.id })} rightIcon={<Icon as={Ionicons} name="play-outline"/>}>
|
||||
<Translate translationKey='playBtn'/>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
+817
-870
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user