[FIX] Reviwed comments on the RP
This commit is contained in:
@@ -66,9 +66,7 @@ export class ValidationError extends Error {
|
||||
|
||||
export default class API {
|
||||
public static readonly baseUrl =
|
||||
process.env.NODE_ENV != 'development' && Platform.OS === 'web'
|
||||
? '/api'
|
||||
: 'https://nightly.chroma.octohub.app/api';
|
||||
Platform.OS === 'web' ? '/api' : process.env.EXPO_PUBLIC_API_URL!;
|
||||
public static async fetch(
|
||||
params: FetchParams,
|
||||
handle: Pick<Required<HandleParams>, 'raw'>
|
||||
|
||||
@@ -31,7 +31,7 @@ export const RawElement = ({ element }: RawElementProps) => {
|
||||
const isSmallScreen = screenSize === 'small';
|
||||
const { width: screenWidth } = useWindowDimensions();
|
||||
const { colors } = useTheme();
|
||||
const IconElement = icon as IconSax;
|
||||
const IconElement = icon;
|
||||
|
||||
return (
|
||||
<Column
|
||||
|
||||
@@ -95,7 +95,7 @@ const ButtonBase: React.FC<ButtonProps> = ({
|
||||
});
|
||||
|
||||
const typeToStyleAnimator = { filled: styleButton, outlined: styleButton, menu: styleMenu };
|
||||
const MyIcon: Icon = icon as Icon;
|
||||
const MyIcon = icon;
|
||||
|
||||
return (
|
||||
<InteractiveBase
|
||||
@@ -104,8 +104,13 @@ const ButtonBase: React.FC<ButtonProps> = ({
|
||||
onPress={async () => {
|
||||
if (onPress && !isDisabled) {
|
||||
setLoading(true);
|
||||
await onPress();
|
||||
setLoading(false);
|
||||
try {
|
||||
await onPress();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}}
|
||||
isDisabled={isDisabled}
|
||||
@@ -124,7 +129,7 @@ const ButtonBase: React.FC<ButtonProps> = ({
|
||||
type === 'menu' ? { justifyContent: 'flex-start' } : {},
|
||||
]}
|
||||
>
|
||||
{icon && (
|
||||
{MyIcon && (
|
||||
<MyIcon
|
||||
size={'18'}
|
||||
color={
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { StyleSheet, ActivityIndicator, View, Image, StyleProp, ViewStyle } from 'react-native';
|
||||
import InteractiveBase from './InteractiveBase';
|
||||
import { Text, useTheme } from 'native-base';
|
||||
import { Icon } from 'iconsax-react-native';
|
||||
import useColorScheme from '../../hooks/colorScheme';
|
||||
|
||||
export type ButtonType = 'filled' | 'outlined' | 'menu';
|
||||
|
||||
interface ButtonProps {
|
||||
title?: string;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
onPress?: () => Promise<void>;
|
||||
isDisabled?: boolean;
|
||||
icon?: Icon;
|
||||
iconVariant?: 'Bold' | 'Outline';
|
||||
iconImage?: string;
|
||||
type?: ButtonType;
|
||||
}
|
||||
|
||||
const ButtonBase: React.FC<ButtonProps> = ({
|
||||
title,
|
||||
style,
|
||||
onPress,
|
||||
isDisabled,
|
||||
icon,
|
||||
iconImage,
|
||||
type = 'filled',
|
||||
iconVariant = 'Outline',
|
||||
}) => {
|
||||
const { colors } = useTheme();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
const styleButton = StyleSheet.create({
|
||||
Default: {
|
||||
scale: 1,
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4.65,
|
||||
elevation: 8,
|
||||
backgroundColor: colors.primary[400],
|
||||
},
|
||||
onHover: {
|
||||
scale: 1.02,
|
||||
shadowOpacity: 0.37,
|
||||
shadowRadius: 7.49,
|
||||
elevation: 12,
|
||||
backgroundColor: colors.primary[500],
|
||||
},
|
||||
onPressed: {
|
||||
scale: 0.98,
|
||||
shadowOpacity: 0.23,
|
||||
shadowRadius: 2.62,
|
||||
elevation: 4,
|
||||
backgroundColor: colors.primary[600],
|
||||
},
|
||||
Disabled: {
|
||||
scale: 1,
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4.65,
|
||||
elevation: 8,
|
||||
backgroundColor: colors.primary[400],
|
||||
},
|
||||
});
|
||||
|
||||
const styleMenu = StyleSheet.create({
|
||||
Default: {
|
||||
scale: 1,
|
||||
shadowOpacity: 0,
|
||||
shadowRadius: 0,
|
||||
elevation: 0,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
onHover: {
|
||||
scale: 1.01,
|
||||
shadowOpacity: 0.37,
|
||||
shadowRadius: 7.49,
|
||||
elevation: 12,
|
||||
backgroundColor: colors.coolGray[400],
|
||||
},
|
||||
onPressed: {
|
||||
scale: 0.99,
|
||||
shadowOpacity: 0.23,
|
||||
shadowRadius: 2.62,
|
||||
elevation: 4,
|
||||
backgroundColor: colors.coolGray[600],
|
||||
},
|
||||
Disabled: {
|
||||
scale: 1,
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4.65,
|
||||
elevation: 8,
|
||||
backgroundColor: colors.coolGray[500],
|
||||
},
|
||||
});
|
||||
|
||||
const typeToStyleAnimator = { filled: styleButton, outlined: styleButton, menu: styleMenu };
|
||||
const MyIcon: Icon = icon as Icon;
|
||||
|
||||
return (
|
||||
<InteractiveBase
|
||||
style={[styles.container, style]}
|
||||
styleAnimate={typeToStyleAnimator[type]}
|
||||
onPress={async () => {
|
||||
if (onPress && !isDisabled) {
|
||||
setLoading(true);
|
||||
await onPress();
|
||||
setLoading(false);
|
||||
}
|
||||
}}
|
||||
isDisabled={isDisabled}
|
||||
isOutlined={type === 'outlined'}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator
|
||||
style={styles.content}
|
||||
size="small"
|
||||
color={type === 'outlined' ? '#6075F9' : '#FFFFFF'}
|
||||
/>
|
||||
) : (
|
||||
<View
|
||||
style={[
|
||||
styles.content,
|
||||
type === 'menu' ? { justifyContent: 'flex-start' } : {},
|
||||
]}
|
||||
>
|
||||
{icon && (
|
||||
<MyIcon
|
||||
size={'18'}
|
||||
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>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</InteractiveBase>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
content: {
|
||||
padding: 10,
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
icon: {
|
||||
width: 18,
|
||||
height: 18,
|
||||
},
|
||||
text: {
|
||||
marginHorizontal: 8,
|
||||
},
|
||||
});
|
||||
|
||||
export default ButtonBase;
|
||||
@@ -11,13 +11,12 @@ type GlassmorphismCCProps = {
|
||||
|
||||
const GlassmorphismCC = ({ children, style }: GlassmorphismCCProps) => {
|
||||
const colorScheme = useColorScheme();
|
||||
console.log(colorScheme);
|
||||
|
||||
return (
|
||||
<BlurView
|
||||
style={[{ borderRadius: 12 }, style]}
|
||||
intensity={60}
|
||||
tint={colorScheme === 'light' ? 'light' : 'dark'}
|
||||
tint={colorScheme}
|
||||
>
|
||||
{children}
|
||||
</BlurView>
|
||||
|
||||
@@ -39,7 +39,7 @@ export interface MusicItemType {
|
||||
onLike: () => void;
|
||||
|
||||
/** Callback function triggered when the song is played. */
|
||||
onPlay?: () => void;
|
||||
onPlay: () => void;
|
||||
}
|
||||
|
||||
// Custom hook to handle the number formatting based on the current user's language.
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ActivityIndicator, StyleSheet } from 'react-native';
|
||||
import MusicItem, { MusicItemType } from './MusicItem';
|
||||
import ButtonBase from './ButtonBase';
|
||||
import { ArrowDown2, Chart2, ArrowRotateLeft, Cup, Icon } from 'iconsax-react-native';
|
||||
import { translate } from '../../i18n/i18n';
|
||||
|
||||
// Props type definition for MusicItemTitle.
|
||||
interface MusicItemTitleProps {
|
||||
@@ -108,7 +109,6 @@ function MusicListComponent({
|
||||
// 'currentPage': current page in pagination.
|
||||
// 'loading': indicates if more items are being loaded.
|
||||
// 'hasMoreMusics': flag for more items availability.
|
||||
console.log('initialMusics', initialMusics.length);
|
||||
const [musicListState, setMusicListState] = useState({
|
||||
allMusics: initialMusics,
|
||||
displayedMusics: initialMusics.slice(0, musicsPerPage),
|
||||
@@ -119,7 +119,6 @@ function MusicListComponent({
|
||||
const { colors } = useTheme();
|
||||
const screenSize = useBreakpointValue({ base: 'small', md: 'md', xl: 'xl' });
|
||||
const isBigScreen = screenSize === 'xl';
|
||||
console.log('coucou', initialMusics.length);
|
||||
|
||||
// Loads additional music items.
|
||||
// Uses useCallback to avoid unnecessary redefinitions on re-renders.
|
||||
@@ -174,12 +173,12 @@ function MusicListComponent({
|
||||
paddingRight: 60,
|
||||
}}
|
||||
>
|
||||
Song
|
||||
{ translate('musicListTitleSong') }
|
||||
</Text>
|
||||
{[
|
||||
{ text: 'level', icon: Chart2 },
|
||||
{ text: 'lastScore', icon: ArrowRotateLeft },
|
||||
{ text: 'BastScore', icon: Cup },
|
||||
{ text: translate('musicListTitleLevel'), icon: Chart2 },
|
||||
{ text: translate('musicListTitleLastScore'), icon: ArrowRotateLeft },
|
||||
{ text: translate('musicListTitleBestScore'), icon: Cup },
|
||||
].map((value) => (
|
||||
<MusicItemTitle
|
||||
key={value.text + 'key'}
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
import { View, Image } from 'react-native';
|
||||
import { Divider, Text, ScrollView, Flex, Row, Popover, Heading, Button } from 'native-base';
|
||||
import useColorScheme from '../../hooks/colorScheme';
|
||||
import { useQuery, useQueries } from '../../Queries';
|
||||
import API from '../../API';
|
||||
import Song from '../../models/Song';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import ButtonBase from './ButtonBase';
|
||||
import {
|
||||
Cup,
|
||||
Discover,
|
||||
Icon,
|
||||
LogoutCurve,
|
||||
Music,
|
||||
SearchNormal1,
|
||||
Setting2,
|
||||
User,
|
||||
} from 'iconsax-react-native';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { LoadingView } from '../Loading';
|
||||
import { translate } from '../../i18n/i18n';
|
||||
import { unsetAccessToken } from '../../state/UserSlice';
|
||||
import { useNavigation } from '../../Navigation';
|
||||
import Spacer from './Spacer';
|
||||
|
||||
const menu: {
|
||||
title: string;
|
||||
icon: Icon;
|
||||
link: string;
|
||||
}[] = [
|
||||
{ title: 'Discovery', icon: Discover, link: 'HomeNew' },
|
||||
{ title: 'Profile', icon: User, link: 'User' },
|
||||
{ title: 'Music', icon: Music, link: 'Home' },
|
||||
{ title: 'Search', icon: SearchNormal1, link: 'Search' },
|
||||
{ title: 'LeaderBoard', icon: Cup, link: 'Score' },
|
||||
];
|
||||
|
||||
type ScaffoldCCProps = {
|
||||
children?: React.ReactNode;
|
||||
routeName: string;
|
||||
};
|
||||
|
||||
const ScaffoldCC = (props: ScaffoldCCProps) => {
|
||||
const navigation = useNavigation();
|
||||
const userQuery = useQuery(API.getUserInfo);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
if (!userQuery.data || userQuery.isLoading) {
|
||||
return <LoadingView />;
|
||||
}
|
||||
const user = userQuery.data;
|
||||
const layout = useWindowDimensions();
|
||||
const colorScheme = useColorScheme();
|
||||
const logo =
|
||||
colorScheme == 'light'
|
||||
? require('../../assets/icon_light.png')
|
||||
: require('../../assets/icon_dark.png');
|
||||
const playHistoryQuery = useQuery(API.getUserPlayHistory);
|
||||
const songHistory = useQueries(
|
||||
playHistoryQuery.data?.map(({ songID }) => API.getSong(songID)) ?? []
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex style={{ flex: 1 }}>
|
||||
<View style={{ height: '100%', flexDirection: 'row', overflow: 'hidden' }}>
|
||||
<View
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '300px',
|
||||
height: '100vh',
|
||||
maxHeight: '100vh',
|
||||
padding: '32px',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<View style={{ width: '100%' }}>
|
||||
<Row>
|
||||
<Image
|
||||
source={{ uri: logo }}
|
||||
style={{
|
||||
aspectRatio: 1,
|
||||
width: 32,
|
||||
height: 32,
|
||||
}}
|
||||
/>
|
||||
{layout.width > 650 && (
|
||||
<Text fontSize={'xl'} selectable={false}>
|
||||
Chromacase
|
||||
</Text>
|
||||
)}
|
||||
</Row>
|
||||
<Spacer height="xl" />
|
||||
<View style={{ width: '100%' }}>
|
||||
{menu.map((value) => (
|
||||
<View key={'key-menu-link-' + value.title}>
|
||||
<ButtonBase
|
||||
style={{ width: '100%' }}
|
||||
type="menu"
|
||||
icon={value.icon}
|
||||
title={value.title}
|
||||
isDisabled={props.routeName === value.link}
|
||||
iconVariant={
|
||||
props.routeName === value.link ? 'Bold' : 'Outline'
|
||||
}
|
||||
onPress={async () =>
|
||||
navigation.navigate(value.link as never)
|
||||
}
|
||||
/>
|
||||
<Spacer />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ width: '100%' }}>
|
||||
<Divider />
|
||||
<Spacer />
|
||||
<Text
|
||||
bold
|
||||
style={{
|
||||
paddingHorizontal: '16px',
|
||||
paddingVertical: '10px',
|
||||
fontSize: 20,
|
||||
}}
|
||||
>
|
||||
Recently played
|
||||
</Text>
|
||||
{songHistory.length === 0 && (
|
||||
<Text
|
||||
style={{
|
||||
paddingHorizontal: '16px',
|
||||
paddingVertical: '10px',
|
||||
}}
|
||||
>
|
||||
No songs played yet
|
||||
</Text>
|
||||
)}
|
||||
{songHistory
|
||||
.map((h) => h.data)
|
||||
.filter((data): data is Song => data !== undefined)
|
||||
.filter(
|
||||
(song, i, array) =>
|
||||
array.map((s) => s.id).findIndex((id) => id == song.id) == i
|
||||
)
|
||||
.slice(0, 4)
|
||||
.map((histoItem, index) => (
|
||||
<View
|
||||
key={'tab-navigation-other-' + index}
|
||||
style={{
|
||||
paddingHorizontal: '16px',
|
||||
paddingVertical: '10px',
|
||||
}}
|
||||
>
|
||||
<Text numberOfLines={1}>{histoItem.name}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<Spacer />
|
||||
<View style={{ width: '100%' }}>
|
||||
<Divider />
|
||||
<Spacer />
|
||||
<ButtonBase
|
||||
style={{ width: '100%' }}
|
||||
title="Settings"
|
||||
icon={Setting2}
|
||||
type="menu"
|
||||
isDisabled={props.routeName === 'Settings'}
|
||||
iconVariant={props.routeName === 'Settings' ? 'Bold' : 'Outline'}
|
||||
onPress={async () => navigation.navigate('Settings', {})}
|
||||
/>
|
||||
<Spacer />
|
||||
{!user.isGuest && (
|
||||
<ButtonBase
|
||||
style={{ width: '100%' }}
|
||||
icon={LogoutCurve}
|
||||
title={translate('signOutBtn')}
|
||||
type="menu"
|
||||
onPress={async () => {
|
||||
dispatch(unsetAccessToken());
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{user.isGuest && (
|
||||
<Popover
|
||||
trigger={(triggerProps) => (
|
||||
<ButtonBase {...triggerProps}>
|
||||
{translate('signOutBtn')}
|
||||
</ButtonBase>
|
||||
)}
|
||||
>
|
||||
<Popover.Content>
|
||||
<Popover.Arrow />
|
||||
<Popover.Body>
|
||||
<Heading size="md" mb={2}>
|
||||
{translate('Attention')}
|
||||
</Heading>
|
||||
<Text>
|
||||
{translate(
|
||||
'YouAreCurrentlyConnectedWithAGuestAccountWarning'
|
||||
)}
|
||||
</Text>
|
||||
<Button.Group variant="ghost" space={2}>
|
||||
<Button
|
||||
onPress={() => dispatch(unsetAccessToken())}
|
||||
colorScheme="red"
|
||||
>
|
||||
{translate('signOutBtn')}
|
||||
</Button>
|
||||
<Button
|
||||
onPress={() => {
|
||||
navigation.navigate('Login');
|
||||
}}
|
||||
colorScheme="green"
|
||||
>
|
||||
{translate('signUpBtn')}
|
||||
</Button>
|
||||
</Button.Group>
|
||||
</Popover.Body>
|
||||
</Popover.Content>
|
||||
</Popover>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<ScrollView
|
||||
style={{ flex: 1, maxHeight: '100vh' }}
|
||||
contentContainerStyle={{ flex: 1 }}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: 'rgba(16,16,20,0.5)',
|
||||
flex: 1,
|
||||
margin: 8,
|
||||
padding: 20,
|
||||
borderRadius: 12,
|
||||
minHeight: 'fit-content',
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
<LinearGradient
|
||||
colors={['#101014', '#6075F9']}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
zIndex: -2,
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScaffoldCC;
|
||||
@@ -74,7 +74,7 @@ const ScaffoldAuth: FunctionComponent<ScaffoldAuthProps> = ({
|
||||
/>
|
||||
{layout.width > 650 && (
|
||||
<Text fontSize={'xl'} selectable={false}>
|
||||
Chromacase
|
||||
ChromaCase
|
||||
</Text>
|
||||
)}
|
||||
</Row>
|
||||
|
||||
@@ -8,19 +8,14 @@ import { LoadingView } from '../Loading';
|
||||
import ScaffoldDesktopCC from './ScaffoldDesktopCC';
|
||||
import ScaffoldMobileCC from './ScaffoldMobileCC';
|
||||
|
||||
const menu: {
|
||||
type: 'main' | 'sub';
|
||||
title: string;
|
||||
icon: Icon;
|
||||
link: string;
|
||||
}[] = [
|
||||
const menu = [
|
||||
{ type: 'main', title: 'menuDiscovery', icon: Discover, link: 'HomeNew' },
|
||||
{ type: 'main', title: 'menuProfile', icon: User, link: 'User' },
|
||||
{ type: 'main', title: 'menuMusic', icon: Music, link: 'Music' },
|
||||
{ type: 'main', title: 'menuSearch', icon: SearchNormal1, link: 'Search' },
|
||||
{ type: 'main', title: 'menuLeaderBoard', icon: Cup, link: 'Score' },
|
||||
{ type: 'sub', title: 'menuSettings', icon: Setting2, link: 'Settings' },
|
||||
];
|
||||
] as const;
|
||||
|
||||
type ScaffoldCCProps = {
|
||||
children?: React.ReactNode;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-mixed-spaces-and-tabs */
|
||||
import { View, Image } from 'react-native';
|
||||
import { View, Image, TouchableOpacity } from 'react-native';
|
||||
import { Divider, Text, ScrollView, Row, useMediaQuery, useTheme } from 'native-base';
|
||||
import { useQuery, useQueries } from '../../Queries';
|
||||
import API from '../../API';
|
||||
@@ -7,7 +7,7 @@ import Song from '../../models/Song';
|
||||
import ButtonBase from './ButtonBase';
|
||||
import { Icon } from 'iconsax-react-native';
|
||||
import { LoadingView } from '../Loading';
|
||||
import { translate } from '../../i18n/i18n';
|
||||
import { TranslationKey, translate } from '../../i18n/i18n';
|
||||
import { useNavigation } from '../../Navigation';
|
||||
import Spacer from './Spacer';
|
||||
import User from '../../models/User';
|
||||
@@ -20,48 +20,58 @@ type ScaffoldDesktopCCProps = {
|
||||
user: User;
|
||||
logo: string;
|
||||
routeName: string;
|
||||
menu: {
|
||||
menu: readonly{
|
||||
type: 'main' | 'sub';
|
||||
title: string;
|
||||
title: TranslationKey;
|
||||
icon: Icon;
|
||||
link: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
// TODO a tester avec un historique de plus de 3 musics différente mdr !!
|
||||
const SongHistory = (props: { quantity: number }) => {
|
||||
const playHistoryQuery = useQuery(API.getUserPlayHistory);
|
||||
const songHistory = useQueries(
|
||||
playHistoryQuery.data?.map(({ songID }) => API.getSong(songID)) ?? []
|
||||
);
|
||||
const navigation = useNavigation();
|
||||
|
||||
if (!playHistoryQuery.data || playHistoryQuery.isLoading) {
|
||||
const musics =
|
||||
songHistory
|
||||
.map((h) => h.data)
|
||||
.filter((data): data is Song => data !== undefined)
|
||||
.filter(
|
||||
(song, i, array) =>
|
||||
array.map((s) => s.id).findIndex((id) => id == song.id) == i
|
||||
)
|
||||
?.slice(0, props.quantity)
|
||||
.map((song: Song) => (
|
||||
<View
|
||||
key={'short-history-tab' + song.id}
|
||||
style={{
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => navigation.navigate('Song', { songId: song.id })}
|
||||
>
|
||||
<Text numberOfLines={1}>{song.name}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
))
|
||||
|
||||
if (!playHistoryQuery.data || playHistoryQuery.isLoading || !songHistory) {
|
||||
return <LoadingView />;
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
{songHistory.length === 0 ? (
|
||||
{musics.length === 0 ? (
|
||||
<Text style={{ paddingHorizontal: 16 }}>{translate('menuNoSongsPlayedYet')}</Text>
|
||||
) : (
|
||||
songHistory
|
||||
.map((h) => h.data)
|
||||
.filter((data): data is Song => data !== undefined)
|
||||
.filter(
|
||||
(song, i, array) =>
|
||||
array.map((s) => s.id).findIndex((id) => id == song.id) == i
|
||||
)
|
||||
.slice(0, props.quantity + 1)
|
||||
.map((histoItem, index) => (
|
||||
<View
|
||||
key={'tab-navigation-other-' + index}
|
||||
style={{
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
}}
|
||||
>
|
||||
<Text numberOfLines={1}>{histoItem.name}</Text>
|
||||
</View>
|
||||
))
|
||||
musics
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
@@ -148,7 +158,7 @@ const ScaffoldDesktopCC = (props: ScaffoldDesktopCCProps) => {
|
||||
</View>
|
||||
</View>
|
||||
{!isSmallScreen && (
|
||||
<View>
|
||||
<View style={{width: '100%'}}>
|
||||
<Divider my="2" _light={{ bg: colors.black[500] }} _dark={{ bg: '#FFF' }} />
|
||||
<Spacer height="xs" />
|
||||
<Text
|
||||
|
||||
@@ -14,7 +14,7 @@ type ScaffoldMobileCCProps = {
|
||||
logo: string;
|
||||
routeName: string;
|
||||
widthPadding: boolean;
|
||||
menu: {
|
||||
menu: readonly {
|
||||
type: 'main' | 'sub';
|
||||
title: string;
|
||||
icon: Icon;
|
||||
@@ -27,8 +27,6 @@ const ScaffoldMobileCC = (props: ScaffoldMobileCCProps) => {
|
||||
const [isSmallScreen] = useMediaQuery({ maxWidth: 400 });
|
||||
const { colors } = useTheme();
|
||||
|
||||
console.log(isSmallScreen);
|
||||
|
||||
return (
|
||||
<View style={{ height: '100%', flexDirection: 'column', overflow: 'hidden' }}>
|
||||
<ScrollView
|
||||
|
||||
@@ -10,6 +10,7 @@ interface SigninFormProps {
|
||||
onSubmit: (username: string, password: string) => Promise<string>;
|
||||
}
|
||||
|
||||
// TODO not use !!
|
||||
const LoginForm = ({ onSubmit }: SigninFormProps) => {
|
||||
const [formData, setFormData] = React.useState({
|
||||
username: {
|
||||
@@ -47,7 +48,7 @@ const LoginForm = ({ onSubmit }: SigninFormProps) => {
|
||||
<Input
|
||||
isRequired
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
placeholder={translate('formPlaceholderUsername')}
|
||||
autoComplete="username"
|
||||
value={formData.username.value}
|
||||
onChangeText={(t) => {
|
||||
@@ -68,6 +69,7 @@ const LoginForm = ({ onSubmit }: SigninFormProps) => {
|
||||
</FormControl.Label>
|
||||
<Input
|
||||
isRequired
|
||||
placeholder={translate('formPlaceholderPassword')}
|
||||
type="password"
|
||||
autoComplete="password"
|
||||
value={formData.password.value}
|
||||
|
||||
@@ -58,7 +58,7 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
||||
isRequired
|
||||
icon={User}
|
||||
error={formData.username.error}
|
||||
placeholder="Username"
|
||||
placeholder={translate('formPlaceholderUsername')}
|
||||
autoComplete="username-new"
|
||||
value={formData.username.value}
|
||||
onChangeText={(t) => {
|
||||
@@ -75,7 +75,7 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
||||
isRequired
|
||||
icon={Sms}
|
||||
error={formData.email.error}
|
||||
placeholder="Email"
|
||||
placeholder={translate('formPlaceholderEmail')}
|
||||
autoComplete="email"
|
||||
value={formData.email.value}
|
||||
onChangeText={(t) => {
|
||||
@@ -93,7 +93,7 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
||||
isSecret
|
||||
icon={Lock1}
|
||||
error={formData.password.error}
|
||||
placeholder="Password"
|
||||
placeholder={translate('formPlaceholderPassword')}
|
||||
autoComplete="password-new"
|
||||
value={formData.password.value}
|
||||
onChangeText={(t) => {
|
||||
@@ -111,7 +111,7 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
||||
isSecret
|
||||
error={formData.repeatPassword.error}
|
||||
icon={Lock1}
|
||||
placeholder="Repeat password"
|
||||
placeholder={translate('formPlaceholderRepeatPassword')}
|
||||
autoComplete="password-new"
|
||||
value={formData.repeatPassword.value}
|
||||
onChangeText={(t) => {
|
||||
|
||||
@@ -38,6 +38,17 @@ export const en = {
|
||||
levelProgress: 'good notes',
|
||||
score: 'Score',
|
||||
|
||||
// Form
|
||||
formPlaceholderUsername: 'Username',
|
||||
formPlaceholderEmail: "Email",
|
||||
formPlaceholderPassword: 'Password',
|
||||
formPlaceholderRepeatPassword: 'Repeat password',
|
||||
|
||||
// MusicListTitle
|
||||
musicListTitleSong : "Song",
|
||||
musicListTitleLevel: 'Level',
|
||||
musicListTitleLastScore: 'Last Score',
|
||||
musicListTitleBestScore: 'Best Score',
|
||||
// Menu
|
||||
menuDiscovery: 'Discovery',
|
||||
menuProfile: 'Profile',
|
||||
@@ -58,7 +69,7 @@ export const en = {
|
||||
//signin
|
||||
signinPageTitle: 'Welcome !',
|
||||
signinPageParagraph: 'Continue with Google or enter your details.',
|
||||
signinLinkLabel: "You don't have an account? ",
|
||||
signinLinkLabel: "You don't have an account?",
|
||||
signinLinkText: 'Sign up for free.',
|
||||
|
||||
//music
|
||||
@@ -305,8 +316,8 @@ export const fr: typeof en = {
|
||||
songPageBtn: 'Aller sur la page de la chanson',
|
||||
level: 'Niveau',
|
||||
chapters: 'Chapitres',
|
||||
bestScore: 'Meilleur Score',
|
||||
lastScore: 'Dernier Score',
|
||||
bestScore: 'Meilleur',
|
||||
lastScore: 'Dernier',
|
||||
bestStreak: 'Meilleure série',
|
||||
precision: 'Précision',
|
||||
|
||||
@@ -330,6 +341,16 @@ export const fr: typeof en = {
|
||||
longestCombo: 'Combo le plus long : ',
|
||||
favoriteGenre: 'Genre favori : ',
|
||||
|
||||
// Form
|
||||
formPlaceholderUsername : "Nom d'utilisateur",
|
||||
formPlaceholderEmail : "Email",
|
||||
formPlaceholderPassword : 'Mot de passe',
|
||||
formPlaceholderRepeatPassword : 'Répéter le mot de passe',
|
||||
// MusicListTitle
|
||||
musicListTitleSong : "Musique",
|
||||
musicListTitleLevel : "Niveau",
|
||||
musicListTitleLastScore : "Dernier",
|
||||
musicListTitleBestScore : "Meilleur",
|
||||
// Menu
|
||||
menuDiscovery: 'Découverte',
|
||||
menuProfile: 'Profil',
|
||||
@@ -350,7 +371,7 @@ export const fr: typeof en = {
|
||||
//signin
|
||||
signinPageTitle: 'Bienvenue !',
|
||||
signinPageParagraph: 'Continuez avec Google ou entrez vos coordonnées.',
|
||||
signinLinkLabel: "Vous n'avez pas de compte ? ",
|
||||
signinLinkLabel: "Vous n'avez pas de compte ?",
|
||||
signinLinkText: 'Inscrivez-vous gratuitement',
|
||||
|
||||
//music
|
||||
@@ -635,6 +656,17 @@ export const sp: typeof en = {
|
||||
longestCombo: 'combo más largo : ',
|
||||
favoriteGenre: 'genero favorito : ',
|
||||
|
||||
// Form
|
||||
formPlaceholderUsername: 'NombreUsuario',
|
||||
formPlaceholderEmail: "Email",
|
||||
formPlaceholderPassword: 'Contraseña',
|
||||
formPlaceholderRepeatPassword: 'Repetir contraseña',
|
||||
|
||||
// MusicListTitle
|
||||
musicListTitleSong : "Canción",
|
||||
musicListTitleLevel: 'Nivel',
|
||||
musicListTitleLastScore: 'Última',
|
||||
musicListTitleBestScore: 'Mejor',
|
||||
// Menu
|
||||
menuDiscovery: 'Descubrimiento',
|
||||
menuProfile: 'Perfil',
|
||||
@@ -655,7 +687,7 @@ export const sp: typeof en = {
|
||||
//signin
|
||||
signinPageTitle: 'Bienvenido !',
|
||||
signinPageParagraph: 'Continúa con Google o introduce tus datos.',
|
||||
signinLinkLabel: '¿No tienes una cuenta? ',
|
||||
signinLinkLabel: '¿No tienes una cuenta?',
|
||||
signinLinkText: 'Regístrate gratis.',
|
||||
|
||||
//music
|
||||
|
||||
@@ -39,4 +39,6 @@ export const translate = (key: keyof typeof en, language?: AvailableLanguages) =
|
||||
});
|
||||
};
|
||||
|
||||
export type TranslationKey = keyof typeof en;
|
||||
|
||||
export { Translate };
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@react-navigation/native": "^6.1.8",
|
||||
"@react-navigation/native-stack": "^6.9.14",
|
||||
"@reduxjs/toolkit": "^1.9.6",
|
||||
"expo": "^49.0.16",
|
||||
"expo": "~49.0.13",
|
||||
"expo-blur": "~12.4.1",
|
||||
"expo-image-picker": "~14.3.2",
|
||||
"expo-linear-gradient": "~12.3.0",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -69,7 +69,7 @@ const SigninView = () => {
|
||||
key={'signin-form-1'}
|
||||
error={formData.username.error}
|
||||
icon={User}
|
||||
placeholder="Username"
|
||||
placeholder={translate('formPlaceholderUsername')}
|
||||
autoComplete="username"
|
||||
value={formData.username.value}
|
||||
onChangeText={(t) => {
|
||||
@@ -87,7 +87,7 @@ const SigninView = () => {
|
||||
key={'signin-form-2'}
|
||||
error={formData.password.error}
|
||||
icon={Lock1}
|
||||
placeholder="Password"
|
||||
placeholder={translate('formPlaceholderPassword')}
|
||||
autoComplete="password"
|
||||
value={formData.password.value}
|
||||
onChangeText={(t) => {
|
||||
@@ -136,7 +136,7 @@ const SigninView = () => {
|
||||
/>
|
||||
}
|
||||
link={{
|
||||
label: translate('signinLinkLabel'),
|
||||
label: translate('signinLinkLabel') + ' ',
|
||||
text: translate('signinLinkText'),
|
||||
onPress: () => navigation.navigate('Signup'),
|
||||
}}
|
||||
|
||||
@@ -83,7 +83,7 @@ const SignupView = () => {
|
||||
key={'signup-form-1'}
|
||||
error={formData.username.error}
|
||||
icon={User}
|
||||
placeholder="Username"
|
||||
placeholder={translate('formPlaceholderUsername')}
|
||||
autoComplete="username"
|
||||
value={formData.username.value}
|
||||
onChangeText={(t) => {
|
||||
@@ -101,7 +101,7 @@ const SignupView = () => {
|
||||
key={'signup-form-2'}
|
||||
error={formData.email.error}
|
||||
icon={Sms}
|
||||
placeholder="Email"
|
||||
placeholder={translate('formPlaceholderEmail')}
|
||||
autoComplete="email"
|
||||
value={formData.email.value}
|
||||
onChangeText={(t) => {
|
||||
@@ -121,7 +121,7 @@ const SignupView = () => {
|
||||
isSecret
|
||||
error={formData.password.error}
|
||||
icon={Lock1}
|
||||
placeholder="Password"
|
||||
placeholder={translate('formPlaceholderPassword')}
|
||||
autoComplete="password-new"
|
||||
value={formData.password.value}
|
||||
onChangeText={(t) => {
|
||||
@@ -140,7 +140,7 @@ const SignupView = () => {
|
||||
isSecret
|
||||
error={formData.repeatPassword.error}
|
||||
icon={Lock1}
|
||||
placeholder="Repeat password"
|
||||
placeholder={translate('formPlaceholderRepeatPassword')}
|
||||
autoComplete="password-new"
|
||||
value={formData.repeatPassword.value}
|
||||
onChangeText={(t) => {
|
||||
|
||||
@@ -27,6 +27,7 @@ import PremiumSettings from './SettingsPremium';
|
||||
import { RouteProps } from '../../Navigation';
|
||||
import ScaffoldCC from '../../components/UI/ScaffoldCC';
|
||||
import { translate } from '../../i18n/i18n';
|
||||
import { useLanguage } from '../../state/LanguageSlice';
|
||||
|
||||
export const PianoSettings = () => {
|
||||
return (
|
||||
@@ -69,7 +70,7 @@ const SettingsTab = (props: RouteProps<{}>) => {
|
||||
const layout = useWindowDimensions();
|
||||
const [index, setIndex] = React.useState(0);
|
||||
const { colors } = useTheme();
|
||||
const [routes] = React.useState<Route[]>([
|
||||
const routes = ([
|
||||
{ key: 'profile', title: 'settingsTabProfile' },
|
||||
{ key: 'premium', title: 'settingsTabPremium' },
|
||||
{ key: 'preferences', title: 'settingsTabPreferences' },
|
||||
|
||||
Reference in New Issue
Block a user