Merge pull request #82 from Chroma-Case/front/home-page
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
> Why do I have a folder named ".expo" in my project?
|
||||
|
||||
The ".expo" folder is created when an Expo project is started using "expo start" command.
|
||||
|
||||
> What do the files contain?
|
||||
|
||||
- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
|
||||
- "packager-info.json": contains port numbers and process PIDs that are used to serve the application to the mobile device/simulator.
|
||||
- "settings.json": contains the server configuration that is used to serve the application manifest.
|
||||
|
||||
> Should I commit the ".expo" folder?
|
||||
|
||||
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
|
||||
|
||||
Upon project creation, the ".expo" folder is already added to your ".gitignore" file.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"hostType": "lan",
|
||||
"lanType": "ip",
|
||||
"dev": true,
|
||||
"minify": false,
|
||||
"urlRandomness": null,
|
||||
"https": false
|
||||
}
|
||||
+1
-1
@@ -19,7 +19,7 @@ export default class API {
|
||||
return {
|
||||
name: "User",
|
||||
email: "user@chromacase.com",
|
||||
xp: 0,
|
||||
xp: 2345,
|
||||
premium: false,
|
||||
metrics: {},
|
||||
settings: {},
|
||||
|
||||
+1
-1
@@ -1,3 +1,4 @@
|
||||
import { NativeBaseProvider } from "native-base";
|
||||
import Theme from './Theme';
|
||||
import React from 'react';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
@@ -5,7 +6,6 @@ import { Provider } from 'react-redux';
|
||||
import store from './state/Store';
|
||||
import { Router } from './Navigation';
|
||||
import './i18n/i18n';
|
||||
import { NativeBaseProvider } from "native-base";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { translate } from './i18n/i18n';
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
export const protectedRoutes = <>
|
||||
<Stack.Screen name="Home" component={HomeView} options={{ title: translate('welcome') }} />
|
||||
<Stack.Screen name="Home" component={HomeView} options={{ title: translate('welcome') }}/>
|
||||
<Stack.Screen name="Song" component={SongLobbyView} options={{ title: translate('play') }} />
|
||||
</>;
|
||||
|
||||
|
||||
+1
-2
@@ -4,7 +4,6 @@ import { extendTheme } from 'native-base';
|
||||
* Using the Material Color guidelines
|
||||
*/
|
||||
const Theme = extendTheme({
|
||||
roundness: 10,
|
||||
colors: {
|
||||
primary:
|
||||
{
|
||||
@@ -45,7 +44,7 @@ const Theme = extendTheme({
|
||||
800: '#262626',
|
||||
900: '#0d0d0d',
|
||||
},
|
||||
accent:
|
||||
secondary:
|
||||
{
|
||||
50: '#d8ffff',
|
||||
100: '#acffff',
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useTheme, Box } from 'native-base';
|
||||
import React from 'react';
|
||||
|
||||
export const CardBorderRadius = 10;
|
||||
|
||||
const cardBorder = (theme: ReturnType<typeof useTheme>) => ({
|
||||
borderColor: theme.colors.text[100],
|
||||
borderRadius: CardBorderRadius,
|
||||
borderWidth: 1
|
||||
})
|
||||
|
||||
const Card = (props: any) => {
|
||||
const theme = useTheme();
|
||||
return <Box {...props} style={{ ...props.style, ...cardBorder(theme) }}>
|
||||
{ props.children }
|
||||
</Box>
|
||||
}
|
||||
|
||||
export default Card;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useNavigation } from "@react-navigation/core";
|
||||
import { HStack, VStack, Text, Progress, Pressable } from "native-base";
|
||||
import { translate } from "../i18n/i18n";
|
||||
import Card from './Card';
|
||||
|
||||
type CompetenciesTableProps = {
|
||||
pedalsCompetency: number;
|
||||
rightHandCompetency: number;
|
||||
leftHandCompetency: number;
|
||||
accuracyCompetency: number;
|
||||
arpegeCompetency: number;
|
||||
chordsCompetency: number;
|
||||
}
|
||||
|
||||
const CompetenciesTable = (props: CompetenciesTableProps) => {
|
||||
const navigation = useNavigation();
|
||||
return <Pressable onPress={() => navigation.navigate('User')}>
|
||||
{({ isHovered, isFocused }) => (
|
||||
<Card padding={5} bg={(isHovered || isFocused) ? 'coolGray.200' : undefined }>
|
||||
<HStack space={5} flex={1}>
|
||||
<VStack space={5}>
|
||||
{ Object.keys(props).map((competencyName) => (
|
||||
<Text bold>{translate(competencyName as keyof CompetenciesTableProps)}</Text>
|
||||
))}
|
||||
</VStack>
|
||||
<VStack space={5} flex={1}>
|
||||
{ Object.keys(props).map((competencyName) => (
|
||||
<Progress flex={1} value={props[competencyName as keyof CompetenciesTableProps]} />
|
||||
))}
|
||||
</VStack>
|
||||
</HStack>
|
||||
|
||||
</Card>
|
||||
)}
|
||||
</Pressable>
|
||||
}
|
||||
|
||||
export default CompetenciesTable
|
||||
@@ -3,4 +3,6 @@ import { Spinner } from "native-base";
|
||||
const LoadingComponent = () => {
|
||||
const theme = useTheme();
|
||||
return <Spinner color={theme.colors.primary[500]}/>
|
||||
}
|
||||
}
|
||||
|
||||
export default LoadingComponent;
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from "react";
|
||||
import Card, { CardBorderRadius } from './Card';
|
||||
import { VStack, Text, Image, Pressable } from 'native-base';
|
||||
import { useNavigation } from "@react-navigation/core";
|
||||
type SongCardProps = {
|
||||
albumCover: string;
|
||||
songTitle: string;
|
||||
artistName: string;
|
||||
songId: number
|
||||
}
|
||||
|
||||
const SongCard = (props: SongCardProps) => {
|
||||
const { albumCover, songTitle, artistName, songId } = props;
|
||||
const navigation = useNavigation();
|
||||
return <Pressable onPress={() => navigation.navigate('Song', { songId })}>
|
||||
{({ isHovered, isFocused }) => (
|
||||
<Card
|
||||
shadow={3}
|
||||
flexDirection='column'
|
||||
alignContent='space-around'
|
||||
bg={(isHovered || isFocused) ? 'coolGray.200' : undefined }
|
||||
>
|
||||
<Image
|
||||
style={{ zIndex: 0, aspectRatio: 1, margin: 5, borderRadius: CardBorderRadius}}
|
||||
source={{ uri: "https://i.discogs.com/yHqu3pnLgJq-cVpYNVYu6mE-fbzIrmIRxc6vES5Oi48/rs:fit/g:sm/q:90/h:556/w:600/czM6Ly9kaXNjb2dz/LWRhdGFiYXNlLWlt/YWdlcy9SLTE2NjQ2/ODUwLTE2MDkwNDU5/NzQtNTkxOS5qcGVn.jpeg" }}
|
||||
alt={[props.songTitle, props.artistName].join('-')}
|
||||
/>
|
||||
<VStack padding={3}>
|
||||
<Text bold fontSize='md'>
|
||||
{songTitle}
|
||||
</Text>
|
||||
<Text>
|
||||
{artistName}
|
||||
</Text>
|
||||
</VStack>
|
||||
</Card>
|
||||
)}
|
||||
</Pressable>
|
||||
}
|
||||
|
||||
export default SongCard;
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import SongCard from './SongCard';
|
||||
import { FlatGrid } from 'react-native-super-grid';
|
||||
import { Heading, VStack } from 'native-base';
|
||||
|
||||
type SongCardGrid = {
|
||||
songs: Parameters<typeof SongCard>[0][];
|
||||
maxItemPerRow?: number,
|
||||
heading?: string
|
||||
}
|
||||
|
||||
const SongCardGrid = (props: SongCardGrid) => {
|
||||
return <VStack>
|
||||
<Heading>{props.heading}</Heading>
|
||||
<FlatGrid
|
||||
maxItemsPerRow={props.maxItemPerRow ?? 5}
|
||||
additionalRowStyle={{ justifyContent: 'space-between' }}
|
||||
data={props.songs}
|
||||
renderItem={({ item }) => <SongCard {...item} /> }
|
||||
spacing={20}
|
||||
/>
|
||||
</VStack>
|
||||
|
||||
}
|
||||
|
||||
export default SongCardGrid;
|
||||
@@ -1,23 +1,60 @@
|
||||
export const en = {
|
||||
welcome: 'Welcome',
|
||||
welcomeMessage: 'Welcome back ',
|
||||
signoutBtn: 'Sign out',
|
||||
signinBtn: 'Sign in',
|
||||
changeLanguageBtn: "Change language",
|
||||
searchBtn: "Search",
|
||||
playBtn: 'Play',
|
||||
songPageBtn: 'Go to song page',
|
||||
level: 'Level',
|
||||
chapters: 'Chapters',
|
||||
bestScore: 'Best Score',
|
||||
lastScore: 'Last Score',
|
||||
play: 'Play'
|
||||
play: 'Play',
|
||||
goNextStep: 'Step Up!',
|
||||
mySkillsToImprove: "My Competencies to work on",
|
||||
recentlyPlayed: 'Recently played',
|
||||
search: 'Search',
|
||||
lastSearched: "Last searched",
|
||||
levelProgress: 'good notes',
|
||||
|
||||
// competencies
|
||||
pedalsCompetency: 'Pedals',
|
||||
rightHandCompetency: 'Right hand',
|
||||
leftHandCompetency: 'Left hand',
|
||||
accuracyCompetency: 'Accuracy',
|
||||
arpegeCompetency: 'Arpeges',
|
||||
chordsCompetency: 'Chords',
|
||||
|
||||
};
|
||||
|
||||
export const fr: typeof en = {
|
||||
welcome: 'Bienvenue',
|
||||
welcomeMessage: 'Re-Bonjour ',
|
||||
signoutBtn: 'Se déconnecter',
|
||||
signinBtn: 'Se connecter',
|
||||
changeLanguageBtn: "Changer la langue",
|
||||
searchBtn: "Rechercher",
|
||||
playBtn: 'Jouer',
|
||||
songPageBtn: 'Aller sur la page de la chanson',
|
||||
level: 'Niveau',
|
||||
chapters: 'Chapitres',
|
||||
bestScore: 'Meilleur Score',
|
||||
lastScore: 'Dernier Score',
|
||||
play: 'Jouer'
|
||||
play: 'Jouer',
|
||||
goNextStep: "Passer à l'étape supérieure",
|
||||
mySkillsToImprove: 'Mes Competences à améliorer',
|
||||
recentlyPlayed: "Récemment joués",
|
||||
lastSearched: "Dernières recherches",
|
||||
search: 'Rechercher',
|
||||
levelProgress: 'bonnes notes',
|
||||
|
||||
// competencies
|
||||
pedalsCompetency: 'Pédales',
|
||||
rightHandCompetency: 'Main droite',
|
||||
leftHandCompetency: 'Main gauche',
|
||||
accuracyCompetency: 'Précision',
|
||||
arpegeCompetency: 'Arpèges',
|
||||
chordsCompetency: 'Accords',
|
||||
};
|
||||
@@ -36,6 +36,7 @@
|
||||
"react-native": "0.68.2",
|
||||
"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-testing-library": "^6.0.0",
|
||||
"react-native-web": "0.17.7",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Provider } from 'react-redux';
|
||||
import store from '../state/Store';
|
||||
import { fireEvent, render, screen } from '@testing-library/react-native';
|
||||
|
||||
import HomeView from '../views/HomeView';
|
||||
import HomeView from './HomeView';
|
||||
import { en, fr } from '../i18n/Translations';
|
||||
|
||||
describe('<HomeView />', () => {
|
||||
|
||||
+117
-27
@@ -1,34 +1,124 @@
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import React from "react";
|
||||
import { Center, Button, Text } from "native-base";
|
||||
import { useDispatch, useSelector } from "../state/Store";
|
||||
import { AvailableLanguages, DefaultLanguage, translate } from "../i18n/i18n";
|
||||
import { useLanguage } from "../state/LanguageSlice";
|
||||
import { unsetUserToken } from "../state/UserSlice";
|
||||
import { useQuery } from "react-query";
|
||||
import API from "../API";
|
||||
import LoadingComponent from "../components/Loading";
|
||||
import { Box, ScrollView, Flex, useBreakpointValue, Text, VStack, Progress, Button, useTheme, Heading, Divider } from 'native-base';
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import SongCardGrid from '../components/SongCardGrid';
|
||||
import CompetenciesTable from '../components/CompetenciesTable'
|
||||
import { translate } from "../i18n/i18n";
|
||||
|
||||
const ProgressBar = ({ xp }: { xp: number}) => {
|
||||
const level = Math.floor(xp / 1000);
|
||||
const nextLevel = level + 1;
|
||||
const nextLevelThreshold = nextLevel * 1000;
|
||||
const progessValue = 100 * xp / nextLevelThreshold;
|
||||
|
||||
const HomeView = () => {
|
||||
const dispatch = useDispatch();
|
||||
const navigation = useNavigation();
|
||||
const language: AvailableLanguages = useSelector((state) => state.language.value);
|
||||
return (
|
||||
<Center style={{ flex: 1 }}>
|
||||
<Text style={{ textAlign: "center" }}>This is the Home Screen</Text>
|
||||
<Button variant='ghost' onPress={() => dispatch(unsetUserToken())}>{translate('signoutBtn')}</Button>
|
||||
<Button variant='ghost' onPress={() => {
|
||||
let newLanguage = DefaultLanguage;
|
||||
switch (language) {
|
||||
case 'en':
|
||||
newLanguage = 'fr';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
dispatch(useLanguage(newLanguage));
|
||||
}}>Change language</Button>
|
||||
<Button variant='ghost' onPress={() => navigation.navigate('Song', { songId: 1 })}>Go to Song Page</Button>
|
||||
<Text style={{ textAlign: "center" }}>Current language: {language}</Text>
|
||||
</Center>
|
||||
<VStack alignItems={'center'}>
|
||||
<Text>{`${translate('level')} ${level}`}</Text>
|
||||
<Box w="90%" maxW="400">
|
||||
<Progress value={progessValue} mx="4" />
|
||||
</Box>
|
||||
<Text>{xp} / {nextLevelThreshold} {translate('levelProgress')}</Text>
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
const HomeView = () => {
|
||||
const theme = useTheme();
|
||||
const navigation = useNavigation();
|
||||
const screenSize = useBreakpointValue({ base: 'small', md: "big"});
|
||||
const flexDirection = useBreakpointValue({ base: 'column', xl: "row"});
|
||||
const userQuery = useQuery(['user'], () => API.getUserInfo());
|
||||
|
||||
if (!userQuery.data) {
|
||||
return <Box style={{ flexGrow: 1, justifyContent: 'center' }}>
|
||||
<LoadingComponent/>
|
||||
</Box>
|
||||
}
|
||||
return <ScrollView>
|
||||
<Box style={{ display: 'flex', padding: 30 }}>
|
||||
|
||||
<Box textAlign={ screenSize == 'small' ? 'center' : undefined } style={{ flexDirection, justifyContent: 'center', display: 'flex' }}>
|
||||
<Text fontSize="xl" flex={screenSize == 'small' ? 1 : 2}>{`${translate('welcome')} ${userQuery.data.name}!`} </Text>
|
||||
<Box flex={1}>
|
||||
<ProgressBar xp={userQuery.data.xp}/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box paddingY={5} style={{ flexDirection }}>
|
||||
<Box flex={2}>
|
||||
<SongCardGrid
|
||||
heading={translate('goNextStep')}
|
||||
songs={[ ...Array(4).keys() ].map(() => ({
|
||||
albumCover: "",
|
||||
songTitle: "Song",
|
||||
artistName: "Artist",
|
||||
songId: 1
|
||||
}))}
|
||||
/>
|
||||
|
||||
<Flex style={{ flexDirection }}>
|
||||
<Box flex={1} paddingY={5}>
|
||||
<Heading>{translate('mySkillsToImprove')}</Heading>
|
||||
<Box padding={5}>
|
||||
<CompetenciesTable
|
||||
pedalsCompetency= {Math.random() * 100}
|
||||
rightHandCompetency={Math.random() * 100}
|
||||
leftHandCompetency= {Math.random() * 100}
|
||||
accuracyCompetency= {Math.random() * 100}
|
||||
arpegeCompetency= {Math.random() * 100}
|
||||
chordsCompetency= {Math.random() * 100}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider orientation="vertical" w="1"/>
|
||||
|
||||
<Box flex={1} padding={5}>
|
||||
<SongCardGrid
|
||||
heading={translate('recentlyPlayed')}
|
||||
maxItemPerRow={2}
|
||||
songs={[ ...Array(4).keys() ].map(() => ({
|
||||
albumCover: "",
|
||||
songTitle: "Song",
|
||||
artistName: "Artist",
|
||||
songId: 1
|
||||
}))}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
<VStack padding={5} flex={1} space={10}>
|
||||
|
||||
<Box style={{flexDirection: 'row'}}>
|
||||
|
||||
<Divider orientation="vertical" w="1"/>
|
||||
|
||||
<Box flex="2" padding={5}>
|
||||
<Box style={{ flexDirection: 'row', justifyContent:'center' }}>
|
||||
<Button backgroundColor={theme.colors.secondary[600]} rounded={"full"} size="sm" onPress={() => navigation.navigate('Search')} >{translate('search')}</Button>
|
||||
</Box>
|
||||
|
||||
<SongCardGrid
|
||||
maxItemPerRow={2}
|
||||
heading={translate('lastSearched')}
|
||||
songs={[ ...Array(4).keys() ].map(() => ({
|
||||
albumCover: "",
|
||||
songTitle: "Song",
|
||||
artistName: "Artist",
|
||||
songId: 1
|
||||
}))}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</VStack>
|
||||
</Box>
|
||||
</Box>
|
||||
</ScrollView>
|
||||
|
||||
}
|
||||
|
||||
export default HomeView;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useRoute } from "@react-navigation/native";
|
||||
import { Button, Divider, Box, Center, Image, Text, VStack, PresenceTransition, Icon } from "native-base";
|
||||
import API from "../API";
|
||||
import { useQuery } from 'react-query';
|
||||
import LoadingComponent from "../components/loading";
|
||||
import LoadingComponent from "../components/Loading";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import logo from '../assets/cover.png';
|
||||
import { translate } from "../i18n/i18n";
|
||||
|
||||
@@ -8201,6 +8201,13 @@ react-native-screens@~3.11.1:
|
||||
react-freeze "^1.0.0"
|
||||
warn-once "^0.1.0"
|
||||
|
||||
react-native-super-grid@^4.6.1:
|
||||
version "4.6.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-super-grid/-/react-native-super-grid-4.6.1.tgz#620a59e98375dd5138d3e6618991d09e93cbe318"
|
||||
integrity sha512-YEKN//TT3DZlbz+1m6YqnclYS+T/Qn2ELrZ0fjoXzB2U/AQoBflvtw0VsJkcPkf3RGWLbD1GKbKN6Hz9fPCVfg==
|
||||
dependencies:
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react-native-svg@12.3.0:
|
||||
version "12.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-12.3.0.tgz#40f657c5d1ee366df23f3ec8dae76fd276b86248"
|
||||
|
||||
Reference in New Issue
Block a user