Merge pull request #82 from Chroma-Case/front/home-page

This commit is contained in:
Arthur Jamet
2022-10-17 11:10:58 +01:00
committed by GitHub
17 changed files with 320 additions and 37 deletions
+15
View File
@@ -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.
+8
View File
@@ -0,0 +1,8 @@
{
"hostType": "lan",
"lanType": "ip",
"dev": true,
"minify": false,
"urlRandomness": null,
"https": false
}
+1 -1
View File
@@ -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
View File
@@ -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();
+1 -1
View File
@@ -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
View File
@@ -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',
+19
View File
@@ -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;
+38
View File
@@ -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;
+41
View File
@@ -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;
+26
View File
@@ -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;
+39 -2
View File
@@ -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',
};
+1
View File
@@ -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",
+1 -1
View File
@@ -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
View File
@@ -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;
+1 -1
View File
@@ -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";
+7
View File
@@ -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"