Front: Pretty

This commit is contained in:
Arthur Jamet
2023-10-28 08:09:21 +02:00
parent e499bb2f9f
commit 7067fb9708
28 changed files with 610 additions and 454 deletions

View File

@@ -29,9 +29,7 @@ const ThemeProvider = ({ children }: { children: JSX.Element }) => {
900: 'rgba(16,16,20,0.9)',
};
const glassmorphism = colorScheme === 'light'
? lightGlassmorphism
: darkGlassmorphism
const glassmorphism = colorScheme === 'light' ? lightGlassmorphism : darkGlassmorphism;
return (
<NativeBaseProvider

View File

@@ -1,11 +1,4 @@
import {
Box,
Flex,
Select,
useBreakpointValue,
useTheme,
Wrap,
} from 'native-base';
import { Box, Flex, Select, useBreakpointValue, useTheme, Wrap } from 'native-base';
import { LineChart } from 'react-native-chart-kit';
import { useState } from 'react';
import { useWindowDimensions } from 'react-native';
@@ -16,43 +9,46 @@ import { LoadingView } from './Loading';
const formatScoreDate = (playDate: Date): string => {
// const formattedDate = `${pad(playDate.getDay())}/${pad(playDate.getMonth())}`;
// const formattedTime = `${pad(playDate.getHours())}:${pad(playDate.getMinutes())}`;
// console.log(playDate.toDateString());
// console.log(`${playDate.getDate()}/${playDate.getMonth() + 1}`);
return `${playDate.getDate()}`;
};
type GraphProps = {
songId: number,
since: Date
songId: number;
since: Date;
};
const calculateDailyAverages = (scores: { playDate: Date, score: number }[]): { playDate: Date, score: number }[] => {
const dailyScores: { [key: string]: number[] } = {};
const calculateDailyAverages = (
scores: { playDate: Date; score: number }[]
): { playDate: Date; score: number }[] => {
const dailyScores: { [key: string]: number[] } = {};
// Regroupez les scores par date
scores.forEach((score) => {
const date = score.playDate.toISOString().split('T')[0] as string; // Obtenez la date au format 'YYYY-MM-DD'
if (!dailyScores[date]) {
dailyScores[date] = [];
}
dailyScores[date]!.push(score.score);
});
// Regroupez les scores par date
scores.forEach((score) => {
const date = score.playDate.toISOString().split('T')[0] as string; // Obtenez la date au format 'YYYY-MM-DD'
if (!dailyScores[date]) {
dailyScores[date] = [];
}
dailyScores[date]!.push(score.score);
});
// Calculez la moyenne des scores par jour et créez un tableau d'objets avec le format final
const dailyAverages: { playDate: Date, score: number }[] = [];
Object.keys(dailyScores).forEach((date) => {
const oneDayScore = dailyScores[date];
// Calculez la moyenne des scores par jour et créez un tableau d'objets avec le format final
const dailyAverages: { playDate: Date; score: number }[] = [];
Object.keys(dailyScores).forEach((date) => {
const oneDayScore = dailyScores[date];
if (oneDayScore) {
const average = oneDayScore.reduce((total, score) => total + score, 0) / oneDayScore.length;
const average =
oneDayScore.reduce((total, score) => total + score, 0) / oneDayScore.length;
dailyAverages.push({ playDate: new Date(date), score: average });
}
});
});
return dailyAverages;
return dailyAverages;
};
const Graph = ({songId, since}: GraphProps) => {
const Graph = ({ songId, since }: GraphProps) => {
const isSmall = useBreakpointValue({ base: true, md: false });
const theme = useTheme();
const [containerWidth, setContainerWidth] = useState(0);
@@ -63,8 +59,7 @@ const Graph = ({songId, since}: GraphProps) => {
}
const dailyScore = calculateDailyAverages(scoresQuery.data.history);
const scoresToSort = dailyScore
.filter((item: { playDate: Date; }) => item.playDate >= since);
const scoresToSort = dailyScore.filter((item: { playDate: Date }) => item.playDate >= since);
const scores = scoresToSort.sort((a, b) => {
if (a.playDate < b.playDate) {
@@ -81,10 +76,12 @@ const Graph = ({songId, since}: GraphProps) => {
style={{ width: '100%', marginTop: 20 }}
onLayout={(event) => setContainerWidth(event.nativeEvent.layout.width)}
>
{scores && scores.length > 0 &&
{scores && scores.length > 0 && (
<LineChart
data={{
labels: isSmall ? [] : scores.map(({ playDate }) => formatScoreDate(playDate)),
labels: isSmall
? []
: scores.map(({ playDate }) => formatScoreDate(playDate)),
datasets: [
{
data: scores.map(({ score }) => score),
@@ -117,10 +114,10 @@ const Graph = ({songId, since}: GraphProps) => {
}}
bezier
/>
}
)}
</Box>
);
}
};
const ScoreGraph = () => {
const layout = useWindowDimensions();
@@ -158,25 +155,25 @@ const ScoreGraph = () => {
setSelectedSinceDate(threeDaysAgo);
break;
}
}
};
return (
<Flex flex={1}>
<Wrap style={{gap: 16, flexDirection: 'row', justifyContent: 'flex-end'}}>
<Wrap style={{ gap: 16, flexDirection: 'row', justifyContent: 'flex-end' }}>
<Box>
<Select
onValueChange={(selectedValue) => setSelectedSong(songs.data.find(song => selectedValue === song.name)?.id)}
onValueChange={(selectedValue) =>
setSelectedSong(
songs.data.find((song) => selectedValue === song.name)?.id
)
}
defaultValue={songs.data.at(0)?.name}
bgColor={colors.coolGray[500]}
variant="filled"
width={layout.width > 650 ? '200' : '150'}
>
{songs.data.map((option) => (
<Select.Item
key={option.id}
label={option.name}
value={option.name}
/>
<Select.Item key={option.id} label={option.name} value={option.name} />
))}
</Select>
</Box>
@@ -198,12 +195,9 @@ const ScoreGraph = () => {
</Select>
</Box>
</Wrap>
{selectedSong !== undefined &&
<Graph
songId={selectedSong}
since={selectedSinceDate}
/>
}
{selectedSong !== undefined && (
<Graph songId={selectedSong} since={selectedSinceDate} />
)}
</Flex>
);
};

View File

@@ -127,12 +127,25 @@ const ButtonBase: React.FC<ButtonProps> = ({
{icon && (
<MyIcon
size={'18'}
color={type === 'outlined' ? '#6075F9' : colorScheme === 'dark' || type === 'filled' ? '#FFFFFF' : colors.black[500] }
color={
type === 'outlined'
? '#6075F9'
: colorScheme === 'dark' || type === 'filled'
? '#FFFFFF'
: colors.black[500]
}
variant={iconVariant}
/>
)}
{iconImage && <Image source={{ uri: iconImage }} style={styles.icon} />}
{title && <Text style={[styles.text, type === 'filled' ? {color: '#fff'} : {}]} selectable={false}>{title}</Text>}
{title && (
<Text
style={[styles.text, type === 'filled' ? { color: '#fff' } : {}]}
selectable={false}
>
{title}
</Text>
)}
</View>
)}
</InteractiveBase>

View File

@@ -127,12 +127,22 @@ const ButtonBase: React.FC<ButtonProps> = ({
{icon && (
<MyIcon
size={'18'}
color={type === 'outlined' ? '#6075F9' : colorScheme === 'light' ? colors.black[500] : '#FFFFFF'}
color={
type === 'outlined'
? '#6075F9'
: colorScheme === 'light'
? colors.black[500]
: '#FFFFFF'
}
variant={iconVariant}
/>
)}
{iconImage && <Image source={{ uri: iconImage }} style={styles.icon} />}
{title && <Text style={styles.text} selectable={false}>{title}</Text>}
{title && (
<Text style={styles.text} selectable={false}>
{title}
</Text>
)}
</View>
)}
</InteractiveBase>

View File

@@ -54,17 +54,9 @@ const CheckboxBase: React.FC<CheckboxProps> = ({ title, color, style, check, set
>
<View style={styles.content}>
{check ? (
<TickSquare
size="24"
color={color ?? colors.primary[400]}
variant="Bold"
/>
<TickSquare size="24" color={color ?? colors.primary[400]} variant="Bold" />
) : (
<AddSquare
size="24"
color={color ?? colors.primary[400]}
variant="Outline"
/>
<AddSquare size="24" color={color ?? colors.primary[400]} variant="Outline" />
)}
<Text style={styles.text} selectable={false}>
{title}

View File

@@ -5,22 +5,22 @@ import { StyleProp, ViewStyle } from 'react-native';
import useColorScheme from '../../hooks/colorScheme';
type GlassmorphismCCProps = {
children?: ReactNode,
children?: ReactNode;
style?: StyleProp<ViewStyle>;
};
const GlassmorphismCC = ({ children, style }: GlassmorphismCCProps) => {
const colorScheme = useColorScheme();
console.log(colorScheme);
const colorScheme = useColorScheme();
console.log(colorScheme);
return (
<BlurView
style={[{borderRadius: 12}, style]}
intensity={60}
tint={colorScheme === 'light' ? 'light' : 'dark'}
>
{children}
</BlurView>
return (
<BlurView
style={[{ borderRadius: 12 }, style]}
intensity={60}
tint={colorScheme === 'light' ? 'light' : 'dark'}
>
{children}
</BlurView>
);
};

View File

@@ -3,86 +3,88 @@ import { TouchableOpacity, Animated, StyleSheet, Platform } from 'react-native';
import { Column, Text, useTheme } from 'native-base';
interface LinkBaseProps {
text: string;
onPress: () => void;
text: string;
onPress: () => void;
}
const LinkBase: React.FC<LinkBaseProps> = ({ text, onPress }) => {
const underlineHeight = useRef(new Animated.Value(4)).current;
const opacity = useRef(new Animated.Value(1)).current;
const color = useRef(new Animated.Value(1)).current;
const underlineHeight = useRef(new Animated.Value(4)).current;
const opacity = useRef(new Animated.Value(1)).current;
const color = useRef(new Animated.Value(1)).current;
const theme = useTheme();
const handleMouseEnter = () => {
if (Platform.OS === 'web') {
Animated.timing(underlineHeight, {
toValue: 20,
duration: 250,
useNativeDriver: false
}).start();
}
};
const handleMouseEnter = () => {
if (Platform.OS === 'web') {
Animated.timing(underlineHeight, {
toValue: 20,
duration: 250,
useNativeDriver: false,
}).start();
}
};
const handleMouseLeave = () => {
if (Platform.OS === 'web') {
Animated.timing(underlineHeight, {
toValue: 4,
duration: 250,
useNativeDriver: false
}).start();
}
};
const handleMouseLeave = () => {
if (Platform.OS === 'web') {
Animated.timing(underlineHeight, {
toValue: 4,
duration: 250,
useNativeDriver: false,
}).start();
}
};
const handlePressIn = () => {
Animated.timing(opacity, {
toValue: 0.8,
duration: 250,
useNativeDriver: false
}).start();
};
const handlePressIn = () => {
Animated.timing(opacity, {
toValue: 0.8,
duration: 250,
useNativeDriver: false,
}).start();
};
const handlePressOut = () => {
Animated.timing(opacity, {
toValue: 1,
duration: 250,
useNativeDriver: false
}).start();
};
const handlePressOut = () => {
Animated.timing(opacity, {
toValue: 1,
duration: 250,
useNativeDriver: false,
}).start();
};
return (
<TouchableOpacity
style={styles.container}
onPress={onPress}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<Column>
<Text>{text}</Text>
<Animated.View style={[
styles.underline,
{
backgroundColor: theme.colors.primary[400],
height: underlineHeight,
opacity: opacity
}
]}/>
</Column>
</TouchableOpacity>
);
return (
<TouchableOpacity
style={styles.container}
onPress={onPress}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<Column>
<Text>{text}</Text>
<Animated.View
style={[
styles.underline,
{
backgroundColor: theme.colors.primary[400],
height: underlineHeight,
opacity: opacity,
},
]}
/>
</Column>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
container: {
position: 'relative',
},
underline: {
width: '100%',
position: 'absolute',
zIndex: -1,
bottom: 0,
},
container: {
position: 'relative',
},
underline: {
width: '100%',
position: 'absolute',
zIndex: -1,
bottom: 0,
},
});
export default LinkBase;

View File

@@ -6,7 +6,7 @@ import { translate } from '../../i18n/i18n';
import { unsetAccessToken } from '../../state/UserSlice';
import { BlurView } from 'expo-blur';
import { useState } from 'react';
import Modal from "react-native-modal";
import Modal from 'react-native-modal';
import React from 'react';
import SignUpForm from '../../components/forms/signupform';
import API, { APIError } from '../../API';
@@ -26,39 +26,48 @@ const handleSubmit = async (username: string, password: string, email: string) =
type LogoutButtonCCProps = {
collapse?: boolean;
isGuest?: boolean;
style: any;
buttonType: ButtonType
style: any;
buttonType: ButtonType;
};
const LogoutButtonCC = ({collapse = false, isGuest = false, buttonType = 'menu', style}: LogoutButtonCCProps) => {
const LogoutButtonCC = ({
collapse = false,
isGuest = false,
buttonType = 'menu',
style,
}: LogoutButtonCCProps) => {
const dispatch = useDispatch();
const [isVisible, setIsVisible] = useState(false);
return (
<>
<ButtonBase
style={style}
icon={LogoutCurve}
title={collapse ? translate('signOutBtn') : undefined}
type={buttonType}
onPress={async () => {isGuest ? setIsVisible(true) : dispatch(unsetAccessToken());}}
/>
<PopupCC
title={translate('Attention')}
description={translate('transformGuestToUserExplanations')}
isVisible={isVisible}
setIsVisible={setIsVisible}
>
<SignUpForm onSubmit={handleSubmit} />
<ButtonBase
style={!collapse ? { width: '100%' } : {}}
type="outlined"
icon={LogoutCurve}
title={translate('signOutBtn')}
onPress={async () => { dispatch(unsetAccessToken()) }}
/>
</PopupCC>
</>
<>
<ButtonBase
style={style}
icon={LogoutCurve}
title={collapse ? translate('signOutBtn') : undefined}
type={buttonType}
onPress={async () => {
isGuest ? setIsVisible(true) : dispatch(unsetAccessToken());
}}
/>
<PopupCC
title={translate('Attention')}
description={translate('transformGuestToUserExplanations')}
isVisible={isVisible}
setIsVisible={setIsVisible}
>
<SignUpForm onSubmit={handleSubmit} />
<ButtonBase
style={!collapse ? { width: '100%' } : {}}
type="outlined"
icon={LogoutCurve}
title={translate('signOutBtn')}
onPress={async () => {
dispatch(unsetAccessToken());
}}
/>
</PopupCC>
</>
);
};

View File

@@ -3,61 +3,57 @@ import ButtonBase from './ButtonBase';
import { CloseSquare } from 'iconsax-react-native';
import { BlurView } from 'expo-blur';
import { ReactNode } from 'react';
import Modal from "react-native-modal";
import Modal from 'react-native-modal';
import React from 'react';
import GlassmorphismCC from './Glassmorphism';
type PopupCCProps = {
title: string,
description?: string,
children?: ReactNode,
isVisible: boolean,
setIsVisible?: (isVisible: boolean) => void,
title: string;
description?: string;
children?: ReactNode;
isVisible: boolean;
setIsVisible?: (isVisible: boolean) => void;
};
const PopupCC = ({ title, description, children, isVisible, setIsVisible }: PopupCCProps) => {
return (
<Modal
backdropOpacity={0.75}
isVisible={isVisible}
style={{
display: 'flex',
alignContent: 'center',
alignSelf: 'center',
alignItems: 'center'
}}
>
<GlassmorphismCC>
<Column
style={{
maxWidth: '800px',
maxHeight: 'fit-content',
padding: '20px',
}}
space={4}
>
<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 &&
<ButtonBase
type='menu'
style={{width: 'fit-content'}}
icon={CloseSquare}
onPress={async () => setIsVisible(false)}
/>
}
</Row>
</Heading>
{description !== undefined &&
<Text>{description}</Text>
}
{children !== undefined && children}
</Column>
</GlassmorphismCC>
</Modal>
<Modal
backdropOpacity={0.75}
isVisible={isVisible}
style={{
display: 'flex',
alignContent: 'center',
alignSelf: 'center',
alignItems: 'center',
}}
>
<GlassmorphismCC>
<Column
style={{
maxWidth: '800px',
maxHeight: 'fit-content',
padding: '20px',
}}
space={4}
>
<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 && (
<ButtonBase
type="menu"
style={{ width: 'fit-content' }}
icon={CloseSquare}
onPress={async () => setIsVisible(false)}
/>
)}
</Row>
</Heading>
{description !== undefined && <Text>{description}</Text>}
{children !== undefined && children}
</Column>
</GlassmorphismCC>
</Modal>
);
};

View File

@@ -86,11 +86,11 @@ const ScaffoldCC = (props: ScaffoldCCProps) => {
height: 32,
}}
/>
{layout.width > 650 &&
{layout.width > 650 && (
<Text fontSize={'xl'} selectable={false}>
Chromacase
</Text>
}
)}
</Row>
<Spacer height="xl" />
<View style={{ width: '100%' }}>

View File

@@ -1,5 +1,16 @@
import { LinearGradient } from 'expo-linear-gradient';
import { Flex, Stack, View, Text, Wrap, Image, Row, Column, ScrollView, useToast } from 'native-base';
import {
Flex,
Stack,
View,
Text,
Wrap,
Image,
Row,
Column,
ScrollView,
useToast,
} from 'native-base';
import { FunctionComponent } from 'react';
import { Linking, useWindowDimensions } from 'react-native';
import ButtonBase from './ButtonBase';
@@ -37,9 +48,10 @@ const ScaffoldAuth: FunctionComponent<ScaffoldAuthProps> = ({
const dispatch = useDispatch();
const toast = useToast();
const colorScheme = useColorScheme();
const logo = colorScheme == 'light'
? require('../../assets/icon_light.png')
: require('../../assets/icon_dark.png');
const logo =
colorScheme == 'light'
? require('../../assets/icon_light.png')
: require('../../assets/icon_dark.png');
const [banner] = useAssets(require('../../assets/banner.jpg'));
return (
@@ -49,7 +61,7 @@ const ScaffoldAuth: FunctionComponent<ScaffoldAuthProps> = ({
style={{ flex: 1, backgroundColor: '#cdd4fd' }}
>
<Column style={{ flex: 1 }}>
<Wrap space={4} direction='row' style={{padding: 16, paddingBottom: 0}}>
<Wrap space={4} direction="row" style={{ padding: 16, paddingBottom: 0 }}>
<Row space={2} flex={1}>
<Image
source={{ uri: logo }}
@@ -59,14 +71,14 @@ const ScaffoldAuth: FunctionComponent<ScaffoldAuthProps> = ({
height: 32,
}}
/>
{layout.width > 650 &&
{layout.width > 650 && (
<Text fontSize={'xl'} selectable={false}>
Chromacase
</Text>
}
)}
</Row>
<ButtonBase
title='guest mode'
title="guest mode"
onPress={async () => {
try {
handleGuestLogin((accessToken: string) => {
@@ -79,10 +91,17 @@ const ScaffoldAuth: FunctionComponent<ScaffoldAuthProps> = ({
}
toast.show({ description: error as string });
}
}}
}}
/>
</Wrap>
<ScrollView contentContainerStyle={{ padding: 16, flexGrow: 1, justifyContent: 'center', alignSelf: 'center' }}>
<ScrollView
contentContainerStyle={{
padding: 16,
flexGrow: 1,
justifyContent: 'center',
alignSelf: 'center',
}}
>
<View style={{ width: '100%', maxWidth: 420 }}>
<Stack
space={8}
@@ -125,16 +144,13 @@ const ScaffoldAuth: FunctionComponent<ScaffoldAuthProps> = ({
{submitButton}
<Wrap style={{ flexDirection: 'row', justifyContent: 'center' }}>
<Text>{link.label}</Text>
<LinkBase
text={link.text}
onPress={link.onPress}
/>
<LinkBase text={link.text} onPress={link.onPress} />
</Wrap>
</Stack>
</View>
</ScrollView>
</Column>
{layout.width > 650 &&
{layout.width > 650 && (
<View style={{ width: '50%', height: '100%', padding: 16 }}>
<Image
source={{ uri: banner?.at(0)?.uri }}
@@ -142,8 +158,8 @@ const ScaffoldAuth: FunctionComponent<ScaffoldAuthProps> = ({
style={{ width: '100%', height: '100%', borderRadius: 8 }}
/>
</View>
}
{colorScheme === 'dark' &&
)}
{colorScheme === 'dark' && (
<LinearGradient
colors={['#101014', '#6075F9']}
style={{
@@ -153,7 +169,7 @@ const ScaffoldAuth: FunctionComponent<ScaffoldAuthProps> = ({
zIndex: -2,
}}
/>
}
)}
</Flex>
);
};

View File

@@ -3,31 +3,23 @@ import useColorScheme from '../../hooks/colorScheme';
import { useQuery } from '../../Queries';
import API from '../../API';
import { LinearGradient } from 'expo-linear-gradient';
import {
Cup,
Discover,
Icon,
Music,
SearchNormal1,
Setting2,
User,
} from 'iconsax-react-native';
import { Cup, Discover, Icon, Music, SearchNormal1, Setting2, User } from 'iconsax-react-native';
import { LoadingView } from '../Loading';
import ScaffoldDesktopCC from './ScaffoldDesktopCC';
import ScaffoldMobileCC from './ScaffoldMobileCC';
const menu: {
type: "main" | "sub";
type: 'main' | 'sub';
title: string;
icon: Icon;
link: string;
}[] = [
{ type: "main", title: 'menuDiscovery', icon: Discover, link: 'HomeNew' },
{ type: "main", title: 'menuProfile', icon: User, link: 'User' },
{ type: "main", title: 'menuMusic', icon: Music, link: 'Home' },
{ type: "main", title: 'menuSearch', icon: SearchNormal1, link: 'Search' },
{ type: "main", title: 'menuLeaderBoard', icon: Cup, link: 'Score' },
{ type: "sub", title: 'menuSettings', icon: Setting2, link: 'Settings' },
{ type: 'main', title: 'menuDiscovery', icon: Discover, link: 'HomeNew' },
{ type: 'main', title: 'menuProfile', icon: User, link: 'User' },
{ type: 'main', title: 'menuMusic', icon: Music, link: 'Home' },
{ type: 'main', title: 'menuSearch', icon: SearchNormal1, link: 'Search' },
{ type: 'main', title: 'menuLeaderBoard', icon: Cup, link: 'Score' },
{ type: 'sub', title: 'menuSettings', icon: Setting2, link: 'Settings' },
];
type ScaffoldCCProps = {
@@ -36,7 +28,7 @@ type ScaffoldCCProps = {
withPadding?: boolean;
};
const ScaffoldCC = ({children, routeName, withPadding = true}: ScaffoldCCProps) => {
const ScaffoldCC = ({ children, routeName, withPadding = true }: ScaffoldCCProps) => {
const userQuery = useQuery(API.getUserInfo);
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
@@ -44,13 +36,14 @@ const ScaffoldCC = ({children, routeName, withPadding = true}: ScaffoldCCProps)
return <LoadingView />;
}
const colorScheme = useColorScheme();
const logo = colorScheme == 'light'
? require('../../assets/icon_light.png')
: require('../../assets/icon_dark.png');
const logo =
colorScheme == 'light'
? require('../../assets/icon_light.png')
: require('../../assets/icon_dark.png');
return (
<Flex style={{ flex: 1, backgroundColor: '#cdd4fd' }}>
{screenSize === 'small' ?
{screenSize === 'small' ? (
<ScaffoldMobileCC
user={userQuery.data}
logo={logo}
@@ -59,7 +52,8 @@ const ScaffoldCC = ({children, routeName, withPadding = true}: ScaffoldCCProps)
>
{children}
</ScaffoldMobileCC>
: <ScaffoldDesktopCC
) : (
<ScaffoldDesktopCC
user={userQuery.data}
logo={logo}
routeName={routeName}
@@ -68,8 +62,8 @@ const ScaffoldCC = ({children, routeName, withPadding = true}: ScaffoldCCProps)
>
{children}
</ScaffoldDesktopCC>
}
{colorScheme === 'dark' &&
)}
{colorScheme === 'dark' && (
<LinearGradient
colors={['#101014', '#6075F9']}
style={{
@@ -79,7 +73,7 @@ const ScaffoldCC = ({children, routeName, withPadding = true}: ScaffoldCCProps)
zIndex: -2,
}}
/>
}
)}
</Flex>
);
};

View File

@@ -17,20 +17,20 @@ import { ColorSchemeProvider } from '../../Theme';
import useColorScheme from '../../hooks/colorScheme';
type ScaffoldDesktopCCProps = {
widthPadding: boolean,
widthPadding: boolean;
children?: React.ReactNode;
user: User;
logo: string;
routeName: string;
menu: {
type: "main" | "sub";
type: 'main' | 'sub';
title: string;
icon: Icon;
link: string;
}[]
}[];
};
const SongHistory = (props: {quantity: number}) => {
const SongHistory = (props: { quantity: number }) => {
const playHistoryQuery = useQuery(API.getUserPlayHistory);
const songHistory = useQueries(
playHistoryQuery.data?.map(({ songID }) => API.getSong(songID)) ?? []
@@ -42,9 +42,10 @@ const SongHistory = (props: {quantity: number}) => {
return (
<View>
{songHistory.length === 0 ?
{songHistory.length === 0 ? (
<Text style={{ paddingHorizontal: 16 }}>{translate('menuNoSongsPlayedYet')}</Text>
: songHistory
) : (
songHistory
.map((h) => h.data)
.filter((data): data is Song => data !== undefined)
.filter(
@@ -62,12 +63,11 @@ const SongHistory = (props: {quantity: number}) => {
>
<Text numberOfLines={1}>{histoItem.name}</Text>
</View>
)
)
}
))
)}
</View>
);
}
};
const ScaffoldDesktopCC = (props: ScaffoldDesktopCCProps) => {
const navigation = useNavigation();
@@ -103,39 +103,53 @@ const ScaffoldDesktopCC = (props: ScaffoldDesktopCCProps) => {
alignItems: isSmallScreen ? 'center' : 'flex-start',
}}
/>
{!isSmallScreen &&
{!isSmallScreen && (
<Text fontSize={'xl'} selectable={false}>
Chromacase
</Text>
}
)}
</Row>
<Spacer height="lg" />
<View>
{props.menu.map((value) => (
value.type === "main" &&
<View key={'key-menu-link-' + value.title}>
<ButtonBase
style={!isSmallScreen ? { width: '100%' } : {}}
type="menu"
icon={value.icon}
title={!isSmallScreen ? translate(value.title as 'menuDiscovery' | 'menuProfile' | 'menuMusic' | 'menuSearch' | 'menuLeaderBoard' | 'menuSettings') : undefined}
isDisabled={props.routeName === value.link}
iconVariant={
props.routeName === value.link ? 'Bold' : 'Outline'
}
onPress={async () =>
navigation.navigate(value.link as never)
}
/>
<Spacer height='xs'/>
</View>
))}
{props.menu.map(
(value) =>
value.type === 'main' && (
<View key={'key-menu-link-' + value.title}>
<ButtonBase
style={!isSmallScreen ? { width: '100%' } : {}}
type="menu"
icon={value.icon}
title={
!isSmallScreen
? translate(
value.title as
| 'menuDiscovery'
| 'menuProfile'
| 'menuMusic'
| 'menuSearch'
| 'menuLeaderBoard'
| 'menuSettings'
)
: undefined
}
isDisabled={props.routeName === value.link}
iconVariant={
props.routeName === value.link ? 'Bold' : 'Outline'
}
onPress={async () =>
navigation.navigate(value.link as never)
}
/>
<Spacer height="xs" />
</View>
)
)}
</View>
</View>
{!isSmallScreen &&
{!isSmallScreen && (
<View>
<Divider my="2" _light={{bg: colors.black[500]}} _dark={{bg:'#FFF'}}/>
<Spacer height='xs'/>
<Divider my="2" _light={{ bg: colors.black[500] }} _dark={{ bg: '#FFF' }} />
<Spacer height="xs" />
<Text
bold
style={{
@@ -146,38 +160,52 @@ const ScaffoldDesktopCC = (props: ScaffoldDesktopCCProps) => {
>
{translate('menuRecentlyPlayed')}
</Text>
<SongHistory quantity={3}/>
<SongHistory quantity={3} />
</View>
}
<Spacer height='xs'/>
)}
<Spacer height="xs" />
<View style={!isSmallScreen ? { width: '100%' } : {}}>
<Divider my="2" _light={{bg: colors.black[500]}} _dark={{bg: '#FFF'}}/>
<Spacer height='xs'/>
{props.menu.map((value) => (
value.type === "sub" &&
<ButtonBase
key={'key-menu-link-' + value.title}
style={!isSmallScreen ? { width: '100%' } : {}}
type="menu"
icon={value.icon}
title={!isSmallScreen ? translate(value.title as 'menuDiscovery' | 'menuProfile' | 'menuMusic' | 'menuSearch' | 'menuLeaderBoard' | 'menuSettings') : undefined}
isDisabled={props.routeName === value.link}
iconVariant={
props.routeName === value.link ? 'Bold' : 'Outline'
}
onPress={async () =>
navigation.navigate(value.link as never)
}
/>
))}
<Spacer height='xs'/>
<LogoutButtonCC collapse={!isSmallScreen} isGuest={props.user.isGuest} style={!isSmallScreen ? { width: '100%' } : {}} buttonType={'menu'}/>
<Divider my="2" _light={{ bg: colors.black[500] }} _dark={{ bg: '#FFF' }} />
<Spacer height="xs" />
{props.menu.map(
(value) =>
value.type === 'sub' && (
<ButtonBase
key={'key-menu-link-' + value.title}
style={!isSmallScreen ? { width: '100%' } : {}}
type="menu"
icon={value.icon}
title={
!isSmallScreen
? translate(
value.title as
| 'menuDiscovery'
| 'menuProfile'
| 'menuMusic'
| 'menuSearch'
| 'menuLeaderBoard'
| 'menuSettings'
)
: undefined
}
isDisabled={props.routeName === value.link}
iconVariant={
props.routeName === value.link ? 'Bold' : 'Outline'
}
onPress={async () => navigation.navigate(value.link as never)}
/>
)
)}
<Spacer height="xs" />
<LogoutButtonCC
collapse={!isSmallScreen}
isGuest={props.user.isGuest}
style={!isSmallScreen ? { width: '100%' } : {}}
buttonType={'menu'}
/>
</View>
</View>
<ScrollView
style={{ flex: 1, maxHeight: '100vh' }}
contentContainerStyle={{ flex: 1 }}
>
<ScrollView style={{ flex: 1, maxHeight: '100vh' }} contentContainerStyle={{ flex: 1 }}>
<GlassmorphismCC
style={{
backgroundColor: colors.coolGray[500],
@@ -192,10 +220,9 @@ const ScaffoldDesktopCC = (props: ScaffoldDesktopCCProps) => {
>
{props.children}
</GlassmorphismCC>
<Spacer height='xs'/>
<Spacer height="xs" />
</ScrollView>
</View>
);
};

View File

@@ -1,5 +1,5 @@
import { View } from 'react-native';
import { ScrollView, Flex, useMediaQuery, useTheme } from 'native-base';
import { ScrollView, Flex, useMediaQuery, useTheme } from 'native-base';
import ButtonBase from './ButtonBase';
import { Icon } from 'iconsax-react-native';
import { useNavigation } from '../../Navigation';
@@ -13,11 +13,11 @@ type ScaffoldMobileCCProps = {
logo: string;
routeName: string;
menu: {
type: "main" | "sub";
type: 'main' | 'sub';
title: string;
icon: Icon;
link: string;
}[]
}[];
};
const ScaffoldMobileCC = (props: ScaffoldMobileCCProps) => {
@@ -35,16 +35,14 @@ const ScaffoldMobileCC = (props: ScaffoldMobileCCProps) => {
maxHeight: '100vh',
flexDirection: 'column',
flexShrink: 0,
padding: 16
padding: 16,
}}
contentContainerStyle={{ flex: 1 }}
>
<View style={{ flex: 1, minHeight: 'fit-content' }}>
{props.children}
</View>
<Spacer/>
<View style={{ flex: 1, minHeight: 'fit-content' }}>{props.children}</View>
<Spacer />
</ScrollView>
<View style={{padding: 8, paddingTop: 0}}>
<View style={{ padding: 8, paddingTop: 0 }}>
<Flex
style={{
width: '100%',
@@ -61,14 +59,22 @@ const ScaffoldMobileCC = (props: ScaffoldMobileCCProps) => {
key={'key-menu-link-' + value.title}
type="menu"
icon={value.icon}
title={props.routeName === value.link && !isSmallScreen ? translate(value.title as 'menuDiscovery' | 'menuProfile' | 'menuMusic' | 'menuSearch' | 'menuLeaderBoard' | 'menuSettings') : undefined}
title={
props.routeName === value.link && !isSmallScreen
? translate(
value.title as
| 'menuDiscovery'
| 'menuProfile'
| 'menuMusic'
| 'menuSearch'
| 'menuLeaderBoard'
| 'menuSettings'
)
: undefined
}
isDisabled={props.routeName === value.link}
iconVariant={
props.routeName === value.link ? 'Bold' : 'Outline'
}
onPress={async () =>
navigation.navigate(value.link as never)
}
iconVariant={props.routeName === value.link ? 'Bold' : 'Outline'}
onPress={async () => navigation.navigate(value.link as never)}
/>
))}
</Flex>

View File

@@ -27,14 +27,14 @@ const SeparatorBase: FunctionComponent<SeparatorBaseProps> = ({ children }) => {
const colorScheme = useColorScheme();
const { colors } = useTheme();
const color = colorScheme === 'light' ? colors.black[500] : '#FFFFFF';
return (
<View style={styles.container}>
<View style={[styles.line, {backgroundColor: color}]} />
<View style={[styles.line, { backgroundColor: color }]} />
<Text style={styles.text}>{children}</Text>
<View style={[styles.line, {backgroundColor: color}]} />
<View style={[styles.line, { backgroundColor: color }]} />
</View>
);
}
};
export default SeparatorBase;

View File

@@ -104,7 +104,11 @@ const TextFieldBase: React.FC<TextFieldBaseProps> = ({
});
return (
<InteractiveBase style={[style, { borderRadius: 12 }]} styleAnimate={styleAnimate} focusable={false}>
<InteractiveBase
style={[style, { borderRadius: 12 }]}
styleAnimate={styleAnimate}
focusable={false}
>
<View style={styles.container}>
<View style={styles.iconContainerLeft}>
{icon && (
@@ -121,7 +125,9 @@ const TextFieldBase: React.FC<TextFieldBaseProps> = ({
style={[styles.input, icon ? {} : { paddingLeft: 12 }]}
autoComplete={autoComplete}
placeholder={placeholder + (isRequired ? '*' : '')}
placeholderTextColor={colorScheme === 'light' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.7)'}
placeholderTextColor={
colorScheme === 'light' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.7)'
}
secureTextEntry={isSecret ? !isPasswordVisible : false}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}

View File

@@ -130,7 +130,7 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => {
});
}}
/>
<Spacer height='xs'/>
<Spacer height="xs" />
<ButtonBase
style={{ width: '100%' }}
title={translate('signUpBtn')}

View File

@@ -50,14 +50,14 @@ export const en = {
menuNoSongsPlayedYet: 'No songs played yet',
//signup
signupPageTitle: "Create an account",
signupPageParagraph: "Learn to play the piano in an enjoyable and free way.",
signupLinkLabel: "Already have an account? ",
signupLinkText: "Sign in.",
signupPageTitle: 'Create an account',
signupPageParagraph: 'Learn to play the piano in an enjoyable and free way.',
signupLinkLabel: 'Already have an account? ',
signupLinkText: 'Sign in.',
//signin
signinPageTitle: "Welcome !",
signinPageParagraph: "Continue with Google or enter your details.",
signinPageTitle: 'Welcome !',
signinPageParagraph: 'Continue with Google or enter your details.',
signinLinkLabel: "You don't have an account? ",
signinLinkText: 'Sign up for free.',
@@ -185,20 +185,25 @@ export const en = {
settingsProfileTabAvatarSectionUpdateToast: 'Update successful',
settingsProfileTabAvatarSectionUpdateToastError: 'Update failed',
settingsProfileTabChangeEmailSectionTitle: 'Change email',
settingsProfileTabChangeEmailSectionDescription: 'Enter your current email and set your new email',
settingsProfileTabChangeEmailSectionDescription:
'Enter your current email and set your new email',
settingsProfileTabChangePasswordSectionTitle: 'Change password',
settingsProfileTabChangePasswordSectionDescription: 'Enter your current password and set your new password',
settingsProfileTabChangePasswordSectionDescription:
'Enter your current password and set your new password',
// Premium Tab
settingsPremiumTabPremiumAccountSectionTitle: 'Premium account',
settingsPremiumTabPremiumAccountSectionDescription: 'Premium customization and tools to take you to the next level',
settingsPremiumTabPremiumAccountSectionDescription:
'Premium customization and tools to take you to the next level',
settingsPremiumTabPianoMagiqueSectionTitle: 'Magic Piano',
settingsPremiumTabPianoMagiqueSectionDescription: 'Brings light to the piano during games',
settingsPremiumTabPianoMagiqueSectionHelper: 'You must own the Chromacase light module to use this feature',
settingsPremiumTabPianoMagiqueSectionHelper:
'You must own the Chromacase light module to use this feature',
settingsPremiumTabThemePianoSectionTitle: 'Piano Theme',
settingsPremiumTabThemePianoSectionDescription: 'Set your piano theme',
// Preferences Tab
SettingsPreferencesTabThemeSectionTitle: 'Theme',
SettingsPreferencesTabThemeSectionDescription: 'Set the theme (Dark or Light) of your application',
SettingsPreferencesTabThemeSectionDescription:
'Set the theme (Dark or Light) of your application',
SettingsPreferencesTabLanguageSectionTitle: 'Language',
SettingsPreferencesTabLanguageSectionDescription: 'Set the language of your application',
SettingsPreferencesTabDifficultySectionTitle: 'Difficulty',
@@ -206,23 +211,29 @@ export const en = {
SettingsPreferencesTabColorblindModeSectionTitle: 'Colorblind Mode',
SettingsPreferencesTabColorblindModeSectionDescription: 'Increases contrast',
SettingsPreferencesTabMicVolumeSectionTitle: 'Mic Volume',
SettingsPreferencesTabMicVolumeSectionDescription: 'Adjust the volume of your microphone according to your preference',
SettingsPreferencesTabMicVolumeSectionDescription:
'Adjust the volume of your microphone according to your preference',
// Notifications Tab
SettingsNotificationsTabPushNotificationsSectionTitle: 'Push Notifications',
SettingsNotificationsTabPushNotificationsSectionDescription: 'This notification will appear on your device as a pop-up',
SettingsNotificationsTabPushNotificationsSectionDescription:
'This notification will appear on your device as a pop-up',
SettingsNotificationsTabEmailNotificationsSectionTitle: 'Email',
SettingsNotificationsTabEmailNotificationsSectionDescription: 'Receive emails to reach your goals',
SettingsNotificationsTabEmailNotificationsSectionDescription:
'Receive emails to reach your goals',
SettingsNotificationsTabTrainingReminderSectionTitle: 'Training Reminder',
SettingsNotificationsTabTrainingReminderSectionDescription: 'Regular training is the key',
SettingsNotificationsTabReleaseAlertSectionTitle: 'New Release Alerts',
SettingsNotificationsTabReleaseAlertSectionDescription: 'Stay informed of our updates',
// Privacy Tab
SettingsPrivacyTabDataCollectionSectionTitle: 'Data Collection',
SettingsPrivacyTabDataCollectionSectionDescription: 'Do you accept the collection of your data for the improvement of Chromacase?',
SettingsPrivacyTabDataCollectionSectionDescription:
'Do you accept the collection of your data for the improvement of Chromacase?',
SettingsPrivacyTabCustomAdsSectionTitle: 'Customized Ads',
SettingsPrivacyTabCustomAdsSectionDescription: 'Display suggestions in the recommendations section',
SettingsPrivacyTabCustomAdsSectionDescription:
'Display suggestions in the recommendations section',
SettingsPrivacyTabRecommendationsSectionTitle: 'Recommendations',
SettingsPrivacyTabRecommendationsSectionDescription: 'Would you like to receive our advice and recommendations?',
SettingsPrivacyTabRecommendationsSectionDescription:
'Would you like to receive our advice and recommendations?',
SettingsCategoryProfile: 'Profile',
SettingsCategoryPreferences: 'Preferences',
@@ -322,18 +333,18 @@ export const fr: typeof en = {
menuLeaderBoard: 'Classement',
menuSettings: 'Paramètres',
menuRecentlyPlayed : 'Récemment jouée',
menuNoSongsPlayedYet : 'Aucune chanson jouée pour l\'instant',
menuRecentlyPlayed: 'Récemment jouée',
menuNoSongsPlayedYet: "Aucune chanson jouée pour l'instant",
//signup
signupPageTitle: "Créer un compte",
signupPageParagraph: "Apprendre le piano gratuitement et de manière ludique",
signupLinkLabel: "Vous avez déjà un compte ? ",
signupPageTitle: 'Créer un compte',
signupPageParagraph: 'Apprendre le piano gratuitement et de manière ludique',
signupLinkLabel: 'Vous avez déjà un compte ? ',
signupLinkText: "S'identifier",
//signin
signinPageTitle: "Bienvenue !",
signinPageParagraph: "Continuez avec Google ou entrez vos coordonnées.",
signinPageTitle: 'Bienvenue !',
signinPageParagraph: 'Continuez avec Google ou entrez vos coordonnées.',
signinLinkLabel: "Vous n'avez pas de compte ? ",
signinLinkText: 'Inscrivez-vous gratuitement',
@@ -438,8 +449,8 @@ export const fr: typeof en = {
settingsTabPremium: 'Premium',
settingsTabPreferences: 'Preferences',
settingsTabNotifications: 'Notifications',
settingsTabPrivacy: "Confidentialité",
settingsTabPiano: "Piano",
settingsTabPrivacy: 'Confidentialité',
settingsTabPiano: 'Piano',
// Profile Tab
settingsProfileTabGoogleSectionTitle: 'Compte Google',
settingsProfileTabGoogleSectionDescription: 'Liez votre compte Google à ChromaCase',
@@ -450,51 +461,66 @@ export const fr: typeof en = {
settingsProfileTabVerifiedSectionVerifiedText: 'vérifié',
settingsProfileTabVerifiedSectionNotVerifiedText: 'non vérifié',
settingsProfileTabVerifiedSectionVerificationToast: 'Courrier de vérification envoyé',
settingsProfileTabVerifiedSectionVerificationToastError: 'Erreur d\'envoi du courrier de vérification',
settingsProfileTabVerifiedSectionVerificationToastError:
"Erreur d'envoi du courrier de vérification",
settingsProfileTabAvatarSectionTitle: 'Avatar',
settingsProfileTabAvatarSectionDescription: 'Changez votre photo de profil',
settingsProfileTabAvatarSectionChangeItText: 'Modifier',
settingsProfileTabAvatarSectionUpdateToast: 'Mise à jour réussie',
settingsProfileTabAvatarSectionUpdateToastError: 'Échec de la mise à jour',
settingsProfileTabChangeEmailSectionTitle: 'Changer l\'email',
settingsProfileTabChangeEmailSectionDescription: 'Saisissez votre adresse électronique actuelle et définissez votre nouvelle adresse électronique',
settingsProfileTabChangeEmailSectionTitle: "Changer l'email",
settingsProfileTabChangeEmailSectionDescription:
'Saisissez votre adresse électronique actuelle et définissez votre nouvelle adresse électronique',
settingsProfileTabChangePasswordSectionTitle: 'Changer le mot de passe',
settingsProfileTabChangePasswordSectionDescription: 'Saisissez votre mot de passe actuel et définissez votre nouveau mot de passe',
settingsProfileTabChangePasswordSectionDescription:
'Saisissez votre mot de passe actuel et définissez votre nouveau mot de passe',
// Premium Tab
settingsPremiumTabPremiumAccountSectionTitle: 'Compte premium',
settingsPremiumTabPremiumAccountSectionDescription: 'Personnalisation premium et outils vous permettant de passer au niveau supérieur',
settingsPremiumTabPremiumAccountSectionDescription:
'Personnalisation premium et outils vous permettant de passer au niveau supérieur',
settingsPremiumTabPianoMagiqueSectionTitle: 'Piano Magique',
settingsPremiumTabPianoMagiqueSectionDescription: 'Fait apparaître de la lumière sur le piano pendant les parties',
settingsPremiumTabPianoMagiqueSectionHelper: 'Vous devez posséder le module physique lumineux Chromacase pour pouvoir utiliser cette fonctionnalité',
settingsPremiumTabPianoMagiqueSectionDescription:
'Fait apparaître de la lumière sur le piano pendant les parties',
settingsPremiumTabPianoMagiqueSectionHelper:
'Vous devez posséder le module physique lumineux Chromacase pour pouvoir utiliser cette fonctionnalité',
settingsPremiumTabThemePianoSectionTitle: 'Thème de piano',
settingsPremiumTabThemePianoSectionDescription: 'Définissez le thème de votre piano',
// Preferences Tab
SettingsPreferencesTabThemeSectionTitle: 'Thème',
SettingsPreferencesTabThemeSectionDescription: 'Définissez le thème (Sombre ou Clair) de votre application',
SettingsPreferencesTabThemeSectionDescription:
'Définissez le thème (Sombre ou Clair) de votre application',
SettingsPreferencesTabLanguageSectionTitle: 'Langue',
SettingsPreferencesTabLanguageSectionDescription: 'Définissez la langue de votre application',
SettingsPreferencesTabDifficultySectionTitle: 'Difficulté',
SettingsPreferencesTabDifficultySectionDescription: 'La précision du tempo est de plus en plus élevée',
SettingsPreferencesTabDifficultySectionDescription:
'La précision du tempo est de plus en plus élevée',
SettingsPreferencesTabColorblindModeSectionTitle: 'Mode daltonien',
SettingsPreferencesTabColorblindModeSectionDescription: 'Augmente le contraste',
SettingsPreferencesTabMicVolumeSectionTitle: 'Volume du micro',
SettingsPreferencesTabMicVolumeSectionDescription: 'Réglez le volume de votre micro selon vos préférences',
SettingsPreferencesTabMicVolumeSectionDescription:
'Réglez le volume de votre micro selon vos préférences',
// Notifications Tab
SettingsNotificationsTabPushNotificationsSectionTitle: 'Notifications push',
SettingsNotificationsTabPushNotificationsSectionDescription: 'Cette notification apparaîtra sur votre appareil en pop-up',
SettingsNotificationsTabPushNotificationsSectionDescription:
'Cette notification apparaîtra sur votre appareil en pop-up',
SettingsNotificationsTabEmailNotificationsSectionTitle: 'Email',
SettingsNotificationsTabEmailNotificationsSectionDescription: 'Recevez des mails pour atteindre vos objectifs',
SettingsNotificationsTabTrainingReminderSectionTitle: 'Rappel d\'entraînement',
SettingsNotificationsTabTrainingReminderSectionDescription: 'Un apprentissage régulier est la clé',
SettingsNotificationsTabEmailNotificationsSectionDescription:
'Recevez des mails pour atteindre vos objectifs',
SettingsNotificationsTabTrainingReminderSectionTitle: "Rappel d'entraînement",
SettingsNotificationsTabTrainingReminderSectionDescription:
'Un apprentissage régulier est la clé',
SettingsNotificationsTabReleaseAlertSectionTitle: 'Alertes de nouvelles sorties',
SettingsNotificationsTabReleaseAlertSectionDescription: 'Restez informé de nos mises à jour',
// Privacy Tab
SettingsPrivacyTabDataCollectionSectionTitle: 'Collecte de données',
SettingsPrivacyTabDataCollectionSectionDescription: 'Acceptez-vous la récupération de vos données pour l\'amélioration de Chromacase ?',
SettingsPrivacyTabDataCollectionSectionDescription:
"Acceptez-vous la récupération de vos données pour l'amélioration de Chromacase ?",
SettingsPrivacyTabCustomAdsSectionTitle: 'Publicités personnalisées',
SettingsPrivacyTabCustomAdsSectionDescription: 'Afficher les suggestions dans la section des recommandations',
SettingsPrivacyTabCustomAdsSectionDescription:
'Afficher les suggestions dans la section des recommandations',
SettingsPrivacyTabRecommendationsSectionTitle: 'Recommandations',
SettingsPrivacyTabRecommendationsSectionDescription: 'Souhaitez-vous recevoir nos conseils et recommandations ?',
SettingsPrivacyTabRecommendationsSectionDescription:
'Souhaitez-vous recevoir nos conseils et recommandations ?',
SettingsCategoryProfile: 'Profil',
SettingsCategoryPreferences: 'Préférences',
@@ -506,7 +532,7 @@ export const fr: typeof en = {
SettingsCategoryPiano: 'Piano',
transformGuestToUserExplanations:
"Vous êtes actuellement connecté avec un compte invité. La déconnexion entraînera la perte de vos données. Pour sauvegarder votre progression, veuillez vous inscrire.",
'Vous êtes actuellement connecté avec un compte invité. La déconnexion entraînera la perte de vos données. Pour sauvegarder votre progression, veuillez vous inscrire.',
SettingsCategoryGuest: 'Invité',
SettingsNotificationsEmailNotifications: 'Email',
SettingsNotificationsPushNotifications: 'Notifications push',
@@ -611,16 +637,16 @@ export const sp: typeof en = {
menuNoSongsPlayedYet: 'No hay canciones reproducidas todavía',
//signup
signupPageTitle: "Crear una cuenta",
signupPageParagraph: "Aprende a tocar el piano de forma amena y gratuita.",
signupLinkLabel: "¿Ya tienes una cuenta? ",
signupLinkText: "Iniciar sesión.",
signupPageTitle: 'Crear una cuenta',
signupPageParagraph: 'Aprende a tocar el piano de forma amena y gratuita.',
signupLinkLabel: '¿Ya tienes una cuenta? ',
signupLinkText: 'Iniciar sesión.',
//signin
signinPageTitle: "Bienvenido !",
signinPageParagraph: "Continúa con Google o introduce tus datos.",
signinLinkLabel: "¿No tienes una cuenta? ",
signinLinkText: "Regístrate gratis.",
signinPageTitle: 'Bienvenido !',
signinPageParagraph: 'Continúa con Google o introduce tus datos.',
signinLinkLabel: '¿No tienes una cuenta? ',
signinLinkText: 'Regístrate gratis.',
//search
allFilter: 'Todos',
@@ -725,27 +751,34 @@ export const sp: typeof en = {
settingsProfileTabVerifiedSectionVerifiedText: 'verificado',
settingsProfileTabVerifiedSectionNotVerifiedText: 'no verificado',
settingsProfileTabVerifiedSectionVerificationToast: 'Correo de verificación enviado',
settingsProfileTabVerifiedSectionVerificationToastError: 'Error de envío del correo de verificación',
settingsProfileTabVerifiedSectionVerificationToastError:
'Error de envío del correo de verificación',
settingsProfileTabAvatarSectionTitle: 'Avatar',
settingsProfileTabAvatarSectionDescription: 'Cambia tu foto de perfil',
settingsProfileTabAvatarSectionChangeItText: 'Cambiar',
settingsProfileTabAvatarSectionUpdateToast: 'Actualización correcta',
settingsProfileTabAvatarSectionUpdateToastError: 'Actualización fallida',
settingsProfileTabChangeEmailSectionTitle: 'Cambiar correo electrónico',
settingsProfileTabChangeEmailSectionDescription: 'Introduce tu correo electrónico actual y establece tu nuevo correo electrónico',
settingsProfileTabChangeEmailSectionDescription:
'Introduce tu correo electrónico actual y establece tu nuevo correo electrónico',
settingsProfileTabChangePasswordSectionTitle: 'Cambiar contraseña',
settingsProfileTabChangePasswordSectionDescription: 'Introduce tu contraseña actual y establece tu nueva contraseña',
settingsProfileTabChangePasswordSectionDescription:
'Introduce tu contraseña actual y establece tu nueva contraseña',
// Premium Tab
settingsPremiumTabPremiumAccountSectionTitle: 'Cuenta premium',
settingsPremiumTabPremiumAccountSectionDescription: 'Personalización premium y herramientas para llevarte al siguiente nivel',
settingsPremiumTabPremiumAccountSectionDescription:
'Personalización premium y herramientas para llevarte al siguiente nivel',
settingsPremiumTabPianoMagiqueSectionTitle: 'Piano Mágico',
settingsPremiumTabPianoMagiqueSectionDescription: 'Hace aparecer luz en el piano durante los juegos',
settingsPremiumTabPianoMagiqueSectionHelper: 'Debes poseer el módulo de luz Chromacase para usar esta función',
settingsPremiumTabPianoMagiqueSectionDescription:
'Hace aparecer luz en el piano durante los juegos',
settingsPremiumTabPianoMagiqueSectionHelper:
'Debes poseer el módulo de luz Chromacase para usar esta función',
settingsPremiumTabThemePianoSectionTitle: 'Tema de piano',
settingsPremiumTabThemePianoSectionDescription: 'Establece el tema de tu piano',
// Preferences Tab
SettingsPreferencesTabThemeSectionTitle: 'Tema',
SettingsPreferencesTabThemeSectionDescription: 'Establece el tema (Oscuro o Claro) de tu aplicación',
SettingsPreferencesTabThemeSectionDescription:
'Establece el tema (Oscuro o Claro) de tu aplicación',
SettingsPreferencesTabLanguageSectionTitle: 'Idioma',
SettingsPreferencesTabLanguageSectionDescription: 'Establece el idioma de tu aplicación',
SettingsPreferencesTabDifficultySectionTitle: 'Dificultad',
@@ -753,23 +786,31 @@ export const sp: typeof en = {
SettingsPreferencesTabColorblindModeSectionTitle: 'Modo para daltónicos',
SettingsPreferencesTabColorblindModeSectionDescription: 'Aumenta el contraste',
SettingsPreferencesTabMicVolumeSectionTitle: 'Volumen del micrófono',
SettingsPreferencesTabMicVolumeSectionDescription: 'Ajusta el volumen de tu micrófono según tus preferencias',
SettingsPreferencesTabMicVolumeSectionDescription:
'Ajusta el volumen de tu micrófono según tus preferencias',
// Notifications Tab
SettingsNotificationsTabPushNotificationsSectionTitle: 'Notificaciones push',
SettingsNotificationsTabPushNotificationsSectionDescription: 'Esta notificación aparecerá en tu dispositivo como un pop-up',
SettingsNotificationsTabPushNotificationsSectionDescription:
'Esta notificación aparecerá en tu dispositivo como un pop-up',
SettingsNotificationsTabEmailNotificationsSectionTitle: 'Correo electrónico',
SettingsNotificationsTabEmailNotificationsSectionDescription: 'Recibe correos electrónicos para alcanzar tus objetivos',
SettingsNotificationsTabEmailNotificationsSectionDescription:
'Recibe correos electrónicos para alcanzar tus objetivos',
SettingsNotificationsTabTrainingReminderSectionTitle: 'Recordatorio de entrenamiento',
SettingsNotificationsTabTrainingReminderSectionDescription: 'El entrenamiento regular es la clave',
SettingsNotificationsTabTrainingReminderSectionDescription:
'El entrenamiento regular es la clave',
SettingsNotificationsTabReleaseAlertSectionTitle: 'Alertas de nuevos lanzamientos',
SettingsNotificationsTabReleaseAlertSectionDescription: 'Mantente informado de nuestras actualizaciones',
SettingsNotificationsTabReleaseAlertSectionDescription:
'Mantente informado de nuestras actualizaciones',
// Privacy Tab
SettingsPrivacyTabDataCollectionSectionTitle: 'Recolección de datos',
SettingsPrivacyTabDataCollectionSectionDescription: '¿Aceptas la recopilación de tus datos para la mejora de Chromacase?',
SettingsPrivacyTabDataCollectionSectionDescription:
'¿Aceptas la recopilación de tus datos para la mejora de Chromacase?',
SettingsPrivacyTabCustomAdsSectionTitle: 'Anuncios personalizados',
SettingsPrivacyTabCustomAdsSectionDescription: 'Muestra sugerencias en la sección de recomendaciones',
SettingsPrivacyTabCustomAdsSectionDescription:
'Muestra sugerencias en la sección de recomendaciones',
SettingsPrivacyTabRecommendationsSectionTitle: 'Recomendaciones',
SettingsPrivacyTabRecommendationsSectionDescription: '¿Deseas recibir nuestros consejos y recomendaciones?',
SettingsPrivacyTabRecommendationsSectionDescription:
'¿Deseas recibir nuestros consejos y recomendaciones?',
SettingsCategoryProfile: 'Perfil',
SettingsCategoryPreferences: 'Preferencias',

View File

@@ -76,7 +76,13 @@ const ProfileView = (props: RouteProps<{}>) => {
<Text style={{ paddingBottom: 10, fontWeight: 'bold' }}>
Account created on {userQuery.data.data.createdAt.toLocaleDateString()}
</Text>
<Wrap style={{ flexDirection: 'row', alignItems: 'center', paddingBottom: 10 }}>
<Wrap
style={{
flexDirection: 'row',
alignItems: 'center',
paddingBottom: 10,
}}
>
<Text style={{ paddingRight: 20 }}>
Your client ID is {userQuery.data.id}
</Text>
@@ -93,7 +99,7 @@ const ProfileView = (props: RouteProps<{}>) => {
flex={1}
/>
</Row>
<ScoreGraph/>
<ScoreGraph />
</Flex>
</ScaffoldCC>
);

View File

@@ -110,4 +110,4 @@ const SearchView = (props: RouteProps<SearchViewProps>) => {
);
};
export default SearchView;
export default SearchView;

View File

@@ -61,11 +61,11 @@ const SigninView = () => {
return (
<ScaffoldAuth
title={translate("signinPageTitle")}
description={translate("signinPageParagraph")}
title={translate('signinPageTitle')}
description={translate('signinPageParagraph')}
form={[
<TextFormField
style={{width: '100%', flex: 1}}
style={{ width: '100%', flex: 1 }}
key={'signin-form-1'}
error={formData.username.error}
icon={User}

View File

@@ -76,8 +76,8 @@ const SignupView = () => {
return (
<ScaffoldAuth
title={translate("signupPageTitle")}
description={translate("signupPageParagraph")}
title={translate('signupPageTitle')}
description={translate('signupPageParagraph')}
form={[
<TextFormField
key={'signup-form-1'}

View File

@@ -17,13 +17,15 @@ const NotificationsSettings = () => {
}
return (
<ElementList
style={{width: '100%'}}
style={{ width: '100%' }}
elements={[
{
type: 'toggle',
icon: <MonitorMobbile size="24" color={color} style={{ minWidth: 24 }} />,
title: translate('SettingsNotificationsTabPushNotificationsSectionTitle'),
description: translate('SettingsNotificationsTabPushNotificationsSectionDescription'),
description: translate(
'SettingsNotificationsTabPushNotificationsSectionDescription'
),
data: {
value: settings.data.notifications.pushNotif,
onToggle: () => {
@@ -39,7 +41,9 @@ const NotificationsSettings = () => {
type: 'toggle',
icon: <Send2 size="24" color={color} style={{ minWidth: 24 }} />,
title: translate('SettingsNotificationsTabEmailNotificationsSectionTitle'),
description: translate('SettingsNotificationsTabEmailNotificationsSectionDescription'),
description: translate(
'SettingsNotificationsTabEmailNotificationsSectionDescription'
),
data: {
value: settings.data.notifications.emailNotif,
onToggle: () => {
@@ -55,7 +59,9 @@ const NotificationsSettings = () => {
type: 'toggle',
icon: <Calendar1 size="24" color={color} style={{ minWidth: 24 }} />,
title: translate('SettingsNotificationsTabTrainingReminderSectionTitle'),
description: translate('SettingsNotificationsTabTrainingReminderSectionDescription'),
description: translate(
'SettingsNotificationsTabTrainingReminderSectionDescription'
),
data: {
value: settings.data.notifications.trainNotif,
onToggle: () => {
@@ -71,7 +77,9 @@ const NotificationsSettings = () => {
type: 'toggle',
icon: <Warning2 size="24" color={color} style={{ minWidth: 24 }} />,
title: translate('SettingsNotificationsTabReleaseAlertSectionTitle'),
description: translate('SettingsNotificationsTabReleaseAlertSectionDescription'),
description: translate(
'SettingsNotificationsTabReleaseAlertSectionDescription'
),
data: {
value: settings.data.notifications.newSongNotif,
onToggle: () => {

View File

@@ -17,7 +17,7 @@ const PreferencesSettings = () => {
const colorScheme = useColorScheme();
const color = colorScheme === 'light' ? '#000' : '#fff';
return (
<Column space={4} style={{width: '100%'}}>
<Column space={4} style={{ width: '100%' }}>
<ElementList
elements={[
{
@@ -64,7 +64,9 @@ const PreferencesSettings = () => {
icon: <Rank size="24" color={color} style={{ minWidth: 24 }} />,
type: 'dropdown',
title: translate('SettingsPreferencesTabDifficultySectionTitle'),
description: translate('SettingsPreferencesTabDifficultySectionDescription'),
description: translate(
'SettingsPreferencesTabDifficultySectionDescription'
),
data: {
value: settings.difficulty,
defaultValue: 'medium',
@@ -90,7 +92,9 @@ const PreferencesSettings = () => {
icon: <Colorfilter size="24" color={color} style={{ minWidth: 24 }} />,
type: 'toggle',
title: translate('SettingsPreferencesTabColorblindModeSectionTitle'),
description: translate('SettingsPreferencesTabColorblindModeSectionDescription'),
description: translate(
'SettingsPreferencesTabColorblindModeSectionDescription'
),
data: {
value: settings.colorBlind,
onToggle: () => {

View File

@@ -22,7 +22,7 @@ const PrivacySettings = () => {
}
return (
<ElementList
style={{width: '100%'}}
style={{ width: '100%' }}
elements={[
{
type: 'toggle',
@@ -32,9 +32,7 @@ const PrivacySettings = () => {
data: {
value: settings.dataCollection,
onToggle: () =>
dispatch(
updateSettings({ dataCollection: !settings.dataCollection })
),
dispatch(updateSettings({ dataCollection: !settings.dataCollection })),
},
},
{

View File

@@ -21,7 +21,7 @@ const PremiumSettings = () => {
const color = colorScheme === 'light' ? '#000' : '#fff';
return (
<ElementList
style={{width: '100%'}}
style={{ width: '100%' }}
elements={[
{
icon: <Star1 size="24" color={color} style={{ minWidth: 24 }} />,

View File

@@ -36,7 +36,7 @@ const ProfileSettings = () => {
const colorScheme = useColorScheme();
const color = colorScheme === 'light' ? '#000' : '#fff';
return (
<Column space={4} style={{width: '100%'}}>
<Column space={4} style={{ width: '100%' }}>
<ElementList
elements={[
{
@@ -45,7 +45,11 @@ const ProfileSettings = () => {
title: translate('settingsProfileTabGoogleSectionTitle'),
description: translate('settingsProfileTabGoogleSectionDescription'),
data: {
text: translate(user.googleID ? 'settingsProfileTabGoogleSectionLinkedText' : 'settingsProfileTabGoogleSectionNotLinkedText')
text: translate(
user.googleID
? 'settingsProfileTabGoogleSectionLinkedText'
: 'settingsProfileTabGoogleSectionNotLinkedText'
),
},
},
{
@@ -54,20 +58,28 @@ const ProfileSettings = () => {
title: translate('settingsProfileTabVerifiedSectionTitle'),
description: translate('settingsProfileTabVerifiedSectionDescription'),
data: {
text: translate(user.emailVerified ? 'settingsProfileTabVerifiedSectionVerifiedText' : 'settingsProfileTabVerifiedSectionNotVerifiedText'),
text: translate(
user.emailVerified
? 'settingsProfileTabVerifiedSectionVerifiedText'
: 'settingsProfileTabVerifiedSectionNotVerifiedText'
),
onPress: user.emailVerified
? undefined
: () =>
API.fetch({ route: '/auth/reverify', method: 'PUT' })
.then(() =>
Toast.show({
description: translate('settingsProfileTabVerifiedSectionVerificationToast')
description: translate(
'settingsProfileTabVerifiedSectionVerificationToast'
),
})
)
.catch((e) => {
console.error(e);
Toast.show({
description: translate('settingsProfileTabVerifiedSectionVerificationToastError')
description: translate(
'settingsProfileTabVerifiedSectionVerificationToastError'
),
});
}),
},
@@ -94,12 +106,18 @@ const ProfileSettings = () => {
.then(() => {
userQuery.refetch();
Toast.show({
description: translate('settingsProfileTabAvatarSectionUpdateToast'),
description: translate(
'settingsProfileTabAvatarSectionUpdateToast'
),
});
})
.catch((e) => {
console.error(e);
Toast.show({ description: translate('settingsProfileTabAvatarSectionUpdateToastError')});
Toast.show({
description: translate(
'settingsProfileTabAvatarSectionUpdateToastError'
),
});
});
}
});
@@ -125,7 +143,9 @@ const ProfileSettings = () => {
icon: <PasswordCheck size="24" color={color} style={{ minWidth: 24 }} />,
type: 'sectionDropdown',
title: translate('settingsProfileTabChangePasswordSectionTitle'),
description: translate('settingsProfileTabChangePasswordSectionDescription'),
description: translate(
'settingsProfileTabChangePasswordSectionDescription'
),
data: {
value: true,
section: [
@@ -140,7 +160,11 @@ const ProfileSettings = () => {
},
]}
/>
<LogoutButtonCC isGuest={user.isGuest} style={{with: 'fit-content'}} buttonType={'filled'}/>
<LogoutButtonCC
isGuest={user.isGuest}
style={{ with: 'fit-content' }}
buttonType={'filled'}
/>
</Column>
);
};

View File

@@ -89,8 +89,8 @@ const SetttingsNavigator = (props: RouteProps<{}>) => {
borderBottomWidth: 1,
borderColor: colors.primary[500],
}}
activeColor={ colorScheme === 'light' ? '#000' : '#fff'}
inactiveColor={ colorScheme === 'light' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.7)'}
activeColor={colorScheme === 'light' ? '#000' : '#fff'}
inactiveColor={colorScheme === 'light' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.7)'}
indicatorStyle={{ backgroundColor: colors.primary[500] }}
renderIcon={(
scene: Scene<Route> & {
@@ -100,13 +100,25 @@ const SetttingsNavigator = (props: RouteProps<{}>) => {
) => {
const tabHeader = getTabData(scene.route!.key);
return (
<tabHeader.icon size="18" color="#6075F9" variant={scene.focused ? "Bold" : "Outline"} />
<tabHeader.icon
size="18"
color="#6075F9"
variant={scene.focused ? 'Bold' : 'Outline'}
/>
);
}}
renderLabel={({ route, color }) =>
layout.width > 1100 && (
<Text style={{ color: color, paddingLeft: 10, overflow: 'hidden' }}>
{translate(route.title as 'settingsTabProfile' | 'settingsTabPremium' | 'settingsTabPreferences' | 'settingsTabNotifications' | 'settingsTabPrivacy' | 'settingsTabPiano')}
{translate(
route.title as
| 'settingsTabProfile'
| 'settingsTabPremium'
| 'settingsTabPreferences'
| 'settingsTabNotifications'
| 'settingsTabPrivacy'
| 'settingsTabPiano'
)}
</Text>
)
}
@@ -123,7 +135,7 @@ const SetttingsNavigator = (props: RouteProps<{}>) => {
paddingTop: 32,
padding: 20,
maxWidth: 850,
width: '100%'
width: '100%',
}}
style={{ height: 'fit-content' }}
renderTabBar={renderTabBar}