diff --git a/front/Navigation.tsx b/front/Navigation.tsx index 2f98671..7709b20 100644 --- a/front/Navigation.tsx +++ b/front/Navigation.tsx @@ -11,7 +11,6 @@ import { RootState, useSelector } from './state/Store'; import { useDispatch } from 'react-redux'; import { Translate, translate } from './i18n/i18n'; import SongLobbyView from './views/SongLobbyView'; -import StartPageView from './views/StartPageView'; import HomeView from './views/HomeView'; import SearchView from './views/SearchView'; import SetttingsNavigator from './views/settings/SettingsView'; @@ -34,7 +33,6 @@ import SigninView from './views/SigninView'; import SignupView from './views/SignupView'; import PasswordResetView from './views/PasswordResetView'; import ForgotPasswordView from './views/ForgotPasswordView'; -import ScaffoldCC from './components/UI/ScaffoldCC'; import DiscoveryView from './views/V2/DiscoveryView'; // Util function to hide route props in URL @@ -101,11 +99,11 @@ const protectedRoutes = () => const publicRoutes = () => ({ - Start: { - component: StartPageView, - options: { title: 'Chromacase', headerShown: false }, - link: '/', - }, + // Start: { + // component: StartPageView, + // options: { title: 'Chromacase', headerShown: false }, + // link: '/', + // }, Login: { component: SigninView, options: { title: translate('signInBtn'), headerShown: false }, @@ -165,13 +163,9 @@ const RouteToScreen = (props: NativeStackScreenProps) => ( <> - - <> - {component({ ...props.route.params, route: props.route } as Parameters< - Route['component'] - >[0])} - - + {component({ ...props.route.params, route: props.route } as Parameters< + Route['component'] + >[0])} ); diff --git a/front/components/UI/ButtonBase.tsx b/front/components/UI/ButtonBase.tsx index 3704bd5..753b4e7 100644 --- a/front/components/UI/ButtonBase.tsx +++ b/front/components/UI/ButtonBase.tsx @@ -4,6 +4,8 @@ import InteractiveBase from './InteractiveBase'; import { Text, useTheme } from 'native-base'; import { Icon } from 'iconsax-react-native'; +export type ButtonType = 'filled' | 'outlined' | 'menu'; + interface ButtonProps { title?: string; style?: StyleProp; @@ -12,7 +14,7 @@ interface ButtonProps { icon?: Icon; iconVariant?: 'Bold' | 'Outline'; iconImage?: string; - type?: 'filled' | 'outlined' | 'menu'; + type?: ButtonType; } const ButtonBase: React.FC = ({ @@ -128,7 +130,7 @@ const ButtonBase: React.FC = ({ /> )} {iconImage && } - {title && {title}} + {title && {title}} )} diff --git a/front/components/UI/Glassmorphism.tsx b/front/components/UI/Glassmorphism.tsx new file mode 100644 index 0000000..36be5da --- /dev/null +++ b/front/components/UI/Glassmorphism.tsx @@ -0,0 +1,27 @@ +import { BlurView } from 'expo-blur'; +import { ReactNode } from 'react'; +import React from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +import useColorScheme from '../../hooks/colorScheme'; + +type GlassmorphismCCProps = { + children?: ReactNode, + style?: StyleProp; +}; + +const GlassmorphismCC = ({ children, style }: GlassmorphismCCProps) => { + const colorScheme = useColorScheme(); + console.log(colorScheme); + + return ( + + {children} + + ); +}; + +export default GlassmorphismCC; diff --git a/front/components/UI/LogoutButtonCC.tsx b/front/components/UI/LogoutButtonCC.tsx new file mode 100644 index 0000000..aed7426 --- /dev/null +++ b/front/components/UI/LogoutButtonCC.tsx @@ -0,0 +1,64 @@ +import { Text, Row, Heading, Column, Center } from 'native-base'; +import ButtonBase, { ButtonType } from './ButtonBase'; +import { CloseSquare, LoginCurve, LogoutCurve } from 'iconsax-react-native'; +import { useDispatch } from 'react-redux'; +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 React from 'react'; +import SignUpForm from '../../components/forms/signupform'; +import API, { APIError } from '../../API'; +import PopupCC from './PopupCC'; + +const handleSubmit = async (username: string, password: string, email: string) => { + try { + await API.transformGuestToUser({ username, password, email }); + } catch (error) { + if (error instanceof APIError) return translate(error.userMessage); + if (error instanceof Error) return error.message; + return translate('unknownError'); + } + return translate('loggedIn'); +}; + +type LogoutButtonCCProps = { + isGuest?: boolean; + style: any; + buttonType: ButtonType +}; + +const LogoutButtonCC = ({isGuest = false, buttonType = 'menu', style}: LogoutButtonCCProps) => { + const dispatch = useDispatch(); + const [isVisible, setIsVisible] = useState(false); + + return ( + <> + {isGuest ? setIsVisible(true) : dispatch(unsetAccessToken());}} + /> + + + { dispatch(unsetAccessToken()) }} + /> + + + ); +}; + +export default LogoutButtonCC; diff --git a/front/components/UI/PopupCC.tsx b/front/components/UI/PopupCC.tsx new file mode 100644 index 0000000..cb7c40f --- /dev/null +++ b/front/components/UI/PopupCC.tsx @@ -0,0 +1,62 @@ +import { Text, Row, Heading, Column } from 'native-base'; +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 React from 'react'; +import GlassmorphismCC from './Glassmorphism'; + +type PopupCCProps = { + title: string, + description?: string, + children?: ReactNode, + isVisible: boolean, + setIsVisible: (isVisible: boolean) => void, +}; + +const PopupCC = ({ title, description, children, isVisible, setIsVisible }: PopupCCProps) => { + return ( + + + + + + + {title} + + setIsVisible(false)} + /> + + + {description && + {description} + } + {children} + + + + ); +}; + +export default PopupCC; diff --git a/front/components/UI/ScaffoldAuth.tsx b/front/components/UI/ScaffoldAuth.tsx index d9d05b7..05855e2 100644 --- a/front/components/UI/ScaffoldAuth.tsx +++ b/front/components/UI/ScaffoldAuth.tsx @@ -1,13 +1,22 @@ import { LinearGradient } from 'expo-linear-gradient'; -import { Center, Flex, Stack, View, Text, Wrap, Image } 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'; import { translate } from '../../i18n/i18n'; -import API from '../../API'; +import API, { APIError } from '../../API'; import SeparatorBase from './SeparatorBase'; import LinkBase from './LinkBase'; import ImageBanner from '../../assets/banner.jpg'; +import { useDispatch } from '../../state/Store'; +import { setAccessToken } from '../../state/UserSlice'; +import useColorScheme from '../../hooks/colorScheme'; + +const handleGuestLogin = async (apiSetter: (accessToken: string) => void): Promise => { + const apiAccess = await API.createAndGetGuestAccount(); + apiSetter(apiAccess); + return translate('loggedIn'); +}; interface ScaffoldAuthProps { title: string; @@ -25,6 +34,12 @@ const ScaffoldAuth: FunctionComponent = ({ link, }) => { const layout = useWindowDimensions(); + const dispatch = useDispatch(); + const toast = useToast(); + const colorScheme = useColorScheme(); + const logo = colorScheme == 'light' + ? require('../../assets/icon_light.png') + : require('../../assets/icon_dark.png'); return ( = ({ justifyContent="space-between" style={{ flex: 1, backgroundColor: '#101014' }} > -
- - - - {title} - - - {description} - - - - Linking.openURL(`${API.baseUrl}/auth/login/google`)} + + + + - or + {layout.width > 650 && + + Chromacase + + } + + { + try { + handleGuestLogin((accessToken: string) => { + dispatch(setAccessToken(accessToken)); + }); + } catch (error) { + if (error instanceof APIError) { + toast.show({ description: translate(error.userMessage) }); + return; + } + toast.show({ description: error as string }); + } + }} + /> + + + + + {title} + + + {description} + + + - {form} + Linking.openURL(`${API.baseUrl}/auth/login/google`)} + /> + or + + {form} + + {submitButton} + + {link.description} + {link.text} + - {submitButton} - - {link.description} - {link.text} - - - -
+ + + {layout.width > 650 ? ( { +const ScaffoldCC = ({children, routeName, withPadding = true}: ScaffoldCCProps) => { const screenSize = useBreakpointValue({ base: 'small', md: 'big' }); const userQuery = useQuery(API.getUserInfo); @@ -53,19 +53,20 @@ const ScaffoldCC = (props: ScaffoldCCProps) => { return ; } const colorScheme = useColorScheme(); + const logo = colorScheme == 'light' + ? require('../../assets/icon_light.png') + : require('../../assets/icon_dark.png'); if (screenSize === 'small') { return ( - {props.children} + {children} ); } @@ -73,13 +74,12 @@ const ScaffoldCC = (props: ScaffoldCCProps) => { return ( - {props.children} + {children} ); }; diff --git a/front/components/UI/ScaffoldDesktopCC.tsx b/front/components/UI/ScaffoldDesktopCC.tsx index db1d685..f628eb3 100644 --- a/front/components/UI/ScaffoldDesktopCC.tsx +++ b/front/components/UI/ScaffoldDesktopCC.tsx @@ -1,20 +1,21 @@ import { View, Image } from 'react-native'; -import { Divider, Text, ScrollView, Flex, Row, Popover, Heading, Button } from 'native-base'; +import { Divider, Text, ScrollView, Flex, Row } from 'native-base'; 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 { Icon, LogoutCurve } from 'iconsax-react-native'; -import { useDispatch } from 'react-redux'; +import { Icon } from 'iconsax-react-native'; import { LoadingView } from '../Loading'; import { translate } from '../../i18n/i18n'; -import { unsetAccessToken } from '../../state/UserSlice'; import { useNavigation } from '../../Navigation'; import Spacer from './Spacer'; import User from '../../models/User'; +import LogoutButtonCC from './LogoutButtonCC'; +import GlassmorphismCC from './Glassmorphism'; type ScaffoldDesktopCCProps = { + widthPadding: boolean, children?: React.ReactNode; user: User; logo: string; @@ -30,7 +31,6 @@ type ScaffoldDesktopCCProps = { const ScaffoldDesktopCC = (props: ScaffoldDesktopCCProps) => { const navigation = useNavigation(); const userQuery = useQuery(API.getUserInfo); - const dispatch = useDispatch(); if (!userQuery.data || userQuery.isLoading) { return ; @@ -160,75 +160,25 @@ const ScaffoldDesktopCC = (props: ScaffoldDesktopCCProps) => { /> ))} - {!props.user.isGuest && ( - { - dispatch(unsetAccessToken()); - }} - /> - )} - - {props.user.isGuest && ( - ( - - {translate('signOutBtn')} - - )} - > - - - - - {translate('Attention')} - - - {translate( - 'YouAreCurrentlyConnectedWithAGuestAccountWarning' - )} - - - - - - - - - )} + - {props.children} - + diff --git a/front/components/forms/signupform.tsx b/front/components/forms/signupform.tsx index 973d0e5..4b8719d 100644 --- a/front/components/forms/signupform.tsx +++ b/front/components/forms/signupform.tsx @@ -1,10 +1,13 @@ // a form for sign up import React from 'react'; -import { Translate, translate } from '../../i18n/i18n'; +import { translate } from '../../i18n/i18n'; import { string } from 'yup'; -import { FormControl, Input, Stack, WarningOutlineIcon, Box, useToast } from 'native-base'; -import TextButton from '../TextButton'; +import { useToast, Column } from 'native-base'; +import TextFormField from '../UI/TextFormField'; +import { Lock1, Sms, User } from 'iconsax-react-native'; +import ButtonBase from '../UI/ButtonBase'; +import Spacer from '../UI/Spacer'; interface SignupFormProps { onSubmit: (username: string, password: string, email: string) => Promise; @@ -29,7 +32,6 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => { error: null as string | null, }, }); - const [submittingForm, setSubmittingForm] = React.useState(false); const validationSchemas = { username: string() @@ -51,144 +53,111 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => { const toast = useToast(); return ( - - - - - - - - { - let error: null | string = null; - validationSchemas.username - .validate(t) - .catch((e) => (error = e.message)) - .finally(() => { - setFormData({ ...formData, username: { value: t, error } }); - }); - }} - /> - }> - {formData.username.error} - - - - - { - let error: null | string = null; - validationSchemas.email - .validate(t) - .catch((e) => (error = e.message)) - .finally(() => { - setFormData({ ...formData, email: { value: t, error } }); - }); - }} - /> - }> - {formData.email.error} - - - - - { - let error: null | string = null; - validationSchemas.password - .validate(t) - .catch((e) => (error = e.message)) - .finally(() => { - setFormData({ ...formData, password: { value: t, error } }); - }); - }} - /> - }> - {formData.password.error} - - - - - { - let error: null | string = null; - validationSchemas.password - .validate(t) - .catch((e) => (error = e.message)) - .finally(() => { - if (!error && t !== formData.password.value) { - error = translate('passwordsDontMatch'); - } - setFormData({ - ...formData, - repeatPassword: { value: t, error }, - }); - }); - }} - /> - }> - {formData.repeatPassword.error} - - + { + let error: null | string = null; + validationSchemas.username + .validate(t) + .catch((e) => (error = e.message)) + .finally(() => { + setFormData({ ...formData, username: { value: t, error } }); + }); + }} + /> + { + let error: null | string = null; + validationSchemas.email + .validate(t) + .catch((e) => (error = e.message)) + .finally(() => { + setFormData({ ...formData, email: { value: t, error } }); + }); + }} + /> + { + let error: null | string = null; + validationSchemas.password + .validate(t) + .catch((e) => (error = e.message)) + .finally(() => { + setFormData({ ...formData, password: { value: t, error } }); + }); + }} + /> + { + let error: null | string = null; + validationSchemas.password + .validate(t) + .catch((e) => (error = e.message)) + .finally(() => { + if (!error && t !== formData.password.value) { + error = translate('passwordsDontMatch'); } - onPress={async () => { - setSubmittingForm(true); - try { - const resp = await onSubmit( - formData.username.value, - formData.password.value, - formData.email.value - ); - toast.show({ description: resp }); - setSubmittingForm(false); - } catch (e) { - toast.show({ description: e as string }); - setSubmittingForm(false); - } - }} - /> - - - - + setFormData({ + ...formData, + repeatPassword: { value: t, error }, + }); + }); + }} + /> + + { + try { + const resp = await onSubmit( + formData.username.value, + formData.password.value, + formData.email.value + ); + toast.show({ description: resp }); + } catch (e) { + toast.show({ description: e as string }); + } + }} + /> + ); }; diff --git a/front/i18n/Translations.ts b/front/i18n/Translations.ts index 4e0b7c8..af7f00f 100644 --- a/front/i18n/Translations.ts +++ b/front/i18n/Translations.ts @@ -149,7 +149,7 @@ export const en = { SettingsCategoryGuest: 'Guest', transformGuestToUserExplanations: - 'You can transform your guest account to a user account by providing a username and a password. You will then be able to save your progress and access your profile.', + 'You are currently logged in with a guest account. Logging out will result in the loss of your data. To save your progress, please register.', SettingsNotificationsPushNotifications: 'Push', SettingsNotificationsEmailNotifications: 'Email', SettingsNotificationsTrainingReminder: 'Training reminder', @@ -334,7 +334,7 @@ export const fr: typeof en = { SettingsCategoryPiano: 'Piano', transformGuestToUserExplanations: - "Vous êtes actuellement connecté en tant qu'invité. Vous pouvez créer un compte pour sauvegarder vos données et profiter de toutes les fonctionnalités de Chromacase.", + "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', @@ -523,7 +523,7 @@ export const sp: typeof en = { SettingsCategoryPiano: 'Piano', transformGuestToUserExplanations: - 'Actualmente estás conectado como invitado. Puedes crear una cuenta para guardar tus datos y disfrutar de todas las funciones de Chromacase.', + 'Actualmente está conectado con una cuenta de invitado. Si cierra la sesión, perderá sus datos. Para guardar su progreso, regístrese.', SettingsCategoryGuest: 'Invitado', SettingsNotificationsEmailNotifications: 'Email', SettingsNotificationsPushNotifications: 'Notificaciones push', diff --git a/front/package.json b/front/package.json index 3a7aad8..e41145d 100644 --- a/front/package.json +++ b/front/package.json @@ -34,6 +34,7 @@ "add": "^2.0.6", "expo": "^47.0.8", "expo-asset": "~8.7.0", + "expo-blur": "~12.0.1", "expo-dev-client": "~2.0.1", "expo-image-picker": "~14.0.2", "expo-linear-gradient": "~12.0.1", @@ -59,6 +60,7 @@ "react-i18next": "^11.18.3", "react-native": "0.70.5", "react-native-chart-kit": "^6.12.0", + "react-native-modal": "^13.0.1", "react-native-paper": "^4.12.5", "react-native-reanimated": "~2.12.0", "react-native-safe-area-context": "4.4.1", diff --git a/front/views/HomeView.tsx b/front/views/HomeView.tsx index 5ca6808..8c1a25b 100644 --- a/front/views/HomeView.tsx +++ b/front/views/HomeView.tsx @@ -3,7 +3,7 @@ import { useQueries, useQuery } from '../Queries'; import API from '../API'; import { LoadingView } from '../components/Loading'; import { Box, Flex, Stack, Heading, VStack, HStack } from 'native-base'; -import { useNavigation } from '../Navigation'; +import { RouteProps, useNavigation } from '../Navigation'; import SongCardGrid from '../components/SongCardGrid'; import CompetenciesTable from '../components/CompetenciesTable'; import ProgressBar from '../components/ProgressBar'; @@ -11,8 +11,9 @@ import Translate from '../components/Translate'; import TextButton from '../components/TextButton'; import Song from '../models/Song'; import { FontAwesome5 } from '@expo/vector-icons'; +import ScaffoldCC from '../components/UI/ScaffoldCC'; -const HomeView = () => { +const HomeView = (props: RouteProps<{}>) => { const navigation = useNavigation(); const userQuery = useQuery(API.getUserInfo); const playHistoryQuery = useQuery(API.getUserPlayHistory); @@ -39,7 +40,7 @@ const HomeView = () => { return ; } return ( - + { - + ); }; diff --git a/front/views/ProfileView.tsx b/front/views/ProfileView.tsx index ea3a9ed..b794f17 100644 --- a/front/views/ProfileView.tsx +++ b/front/views/ProfileView.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useWindowDimensions } from 'react-native'; import { Column, Flex, Progress, Row, Text, Wrap } from 'native-base'; -import { useNavigation } from '../Navigation'; +import { RouteProps, useNavigation } from '../Navigation'; import UserAvatar from '../components/UserAvatar'; import { LoadingView } from '../components/Loading'; import { useQuery } from '../Queries'; @@ -9,6 +9,7 @@ import API from '../API'; import ButtonBase from '../components/UI/ButtonBase'; import { translate } from '../i18n/i18n'; import ScoreGraph from '../components/ScoreGraph'; +import ScaffoldCC from '../components/UI/ScaffoldCC'; const fakeData = [ { @@ -721,7 +722,7 @@ function xpToProgressBarValue(xp: number): number { return Math.floor(xp / 10); } -const ProfileView = () => { +const ProfileView = (props: RouteProps<{}>) => { const layout = useWindowDimensions(); const navigation = useNavigation(); const userQuery = useQuery(API.getUserInfo); @@ -736,69 +737,71 @@ const ProfileView = () => { const level = xpToLevel(userQuery.data.data.xp); return ( - - 650 ? 'row' : 'column', - alignItems: 'center', - paddingBottom: 20, - justifyContent: 'space-between', - }} - > - 650 ? 'xl' : '2xl'} /> - + + 650 ? 20 : 0, - paddingTop: layout.width > 650 ? 0 : 20, - flex: 1, - width: '100%', + flexDirection: layout.width > 650 ? 'row' : 'column', + alignItems: 'center', + paddingBottom: 20, + justifyContent: 'space-between', }} > - 650 ? 'xl' : '2xl'} /> + 650 ? 20 : 0, + paddingTop: layout.width > 650 ? 0 : 20, + flex: 1, + width: '100%', }} > - - {userQuery.data.name} + + + {userQuery.data.name} + + navigation.navigate('Settings')} + /> + + + Account created on {userQuery.data.data.createdAt.toLocaleDateString()} - navigation.navigate('Settings')} - /> - - - Account created on {userQuery.data.data.createdAt.toLocaleDateString()} - - - - Your client ID is {userQuery.data.id} - - {userQuery.data.data.gamesPlayed} Games played - - - - - {`${translate('level')} ${level}`} - + + Your client ID is {userQuery.data.id} + + {userQuery.data.data.gamesPlayed} Games played + + + + + {`${translate('level')} ${level}`} + + + - - - + + ); }; diff --git a/front/views/SearchView.tsx b/front/views/SearchView.tsx index 4c5bd35..bb43cac 100644 --- a/front/views/SearchView.tsx +++ b/front/views/SearchView.tsx @@ -11,6 +11,7 @@ import { Filter } from '../components/SearchBar'; import { ScrollView } from 'native-base'; import { RouteProps } from '../Navigation'; import LikedSong from '../models/LikedSong'; +import ScaffoldCC from '../components/UI/ScaffoldCC'; interface SearchContextType { filter: 'artist' | 'song' | 'genre' | 'all' | 'favorites'; @@ -81,30 +82,32 @@ const SearchView = (props: RouteProps) => { }; return ( - - - - - - - - + + + + + + + + + + ); }; -export default SearchView; +export default SearchView; \ No newline at end of file diff --git a/front/views/SignupView.tsx b/front/views/SignupView.tsx index 51ea47f..d1739a4 100644 --- a/front/views/SignupView.tsx +++ b/front/views/SignupView.tsx @@ -163,15 +163,15 @@ const SignupView = () => { submitButton={ { diff --git a/front/views/V2/DiscoveryView.tsx b/front/views/V2/DiscoveryView.tsx index eb98e64..5d77aa8 100644 --- a/front/views/V2/DiscoveryView.tsx +++ b/front/views/V2/DiscoveryView.tsx @@ -5,7 +5,8 @@ import { useQuery, useQueries } from '../../Queries'; import HomeMainSongCard from '../../components/V2/HomeMainSongCard'; import SongCardInfo from '../../components/V2/SongCardInfo'; import API from '../../API'; -import { useNavigation } from '../../Navigation'; +import { RouteProps, useNavigation } from '../../Navigation'; +import ScaffoldCC from '../../components/UI/ScaffoldCC'; const bigSideRatio = 1000; const smallSideRatio = 618; @@ -45,7 +46,7 @@ const cards = [ }, ] as [HomeCardProps, HomeCardProps, HomeCardProps, HomeCardProps]; -const HomeView = () => { +const HomeView = (props: RouteProps<{}>) => { const songsQuery = useQuery(API.getSongSuggestions); const screenSize = useBreakpointValue({ base: 'small', md: 'big' }); const isPhone = screenSize === 'small'; @@ -76,36 +77,22 @@ const HomeView = () => { }, [artistsQueries]); return ( - - - + + + - - - { flexGrow: bigSideRatio, }} > - + @@ -128,83 +115,99 @@ const HomeView = () => { flexGrow: bigSideRatio, }} > - + - + + > + + + + + - - - - {'Suggestions'} - - {songsQuery.isLoading && Loading...} - {songsQuery.data?.map((song) => ( - { - navigation.navigate('Song', { songId: song.id }); - }} - onPlay={() => { - console.log('play'); - }} - /> - ))} + + {'Suggestions'} + + {songsQuery.isLoading && Loading...} + + {songsQuery.data?.map((song) => ( + { + navigation.navigate('Song', { songId: song.id }); + }} + onPlay={() => { + console.log('play'); + }} + /> + ))} + - + ); }; diff --git a/front/views/settings/GuestToUserView.tsx b/front/views/settings/GuestToUserView.tsx deleted file mode 100644 index 4f09da2..0000000 --- a/front/views/settings/GuestToUserView.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import SignUpForm from '../../components/forms/signupform'; -import { Center, Heading, Text } from 'native-base'; -import API, { APIError } from '../../API'; -import { translate } from '../../i18n/i18n'; - -const handleSubmit = async (username: string, password: string, email: string) => { - try { - await API.transformGuestToUser({ username, password, email }); - } catch (error) { - if (error instanceof APIError) return translate(error.userMessage); - if (error instanceof Error) return error.message; - return translate('unknownError'); - } - return translate('loggedIn'); -}; - -const GuestToUserView = () => { - return ( -
-
- {translate('signUp')} - - {translate('transformGuestToUserExplanations')} - - - handleSubmit(username, password, email) - } - /> -
-
- ); -}; - -export default GuestToUserView; diff --git a/front/views/settings/NotificationView.tsx b/front/views/settings/NotificationView.tsx deleted file mode 100644 index 508b95b..0000000 --- a/front/views/settings/NotificationView.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React from 'react'; -import { Flex } from 'native-base'; -import { translate } from '../../i18n/i18n'; -import ElementList from '../../components/GtkUI/ElementList'; -import useUserSettings from '../../hooks/userSettings'; -import { LoadingView } from '../../components/Loading'; -import { Calendar1, MonitorMobbile, Send2, Warning2 } from 'iconsax-react-native'; - -const NotificationsView = () => { - const { settings, updateSettings } = useUserSettings(); - - if (!settings.data) { - return ; - } - return ( - - , - title: translate('SettingsNotificationsPushNotifications'), - description: 'Cette notification apparaitra sur votre apparail en pop-up', - data: { - value: settings.data.notifications.pushNotif, - onToggle: () => { - updateSettings({ - notifications: { - pushNotif: !settings.data.notifications.pushNotif, - }, - }); - }, - }, - }, - { - type: 'toggle', - icon: , - title: translate('SettingsNotificationsEmailNotifications'), - description: 'Recevez des mails pour atteindre vos objectifs', - data: { - value: settings.data.notifications.emailNotif, - onToggle: () => { - updateSettings({ - notifications: { - emailNotif: !settings.data.notifications.emailNotif, - }, - }); - }, - }, - }, - { - type: 'toggle', - icon: , - title: translate('SettingsNotificationsTrainingReminder'), - description: 'Un apprentissage régulier est la clé', - data: { - value: settings.data.notifications.trainNotif, - onToggle: () => { - updateSettings({ - notifications: { - trainNotif: !settings.data.notifications.trainNotif, - }, - }); - }, - }, - }, - { - type: 'toggle', - icon: , - title: translate('SettingsNotificationsReleaseAlert'), - description: 'Restez informé de nos mises à jour', - data: { - value: settings.data.notifications.newSongNotif, - onToggle: () => { - updateSettings({ - notifications: { - newSongNotif: !settings.data.notifications.newSongNotif, - }, - }); - }, - }, - }, - ]} - /> - - ); -}; - -export default NotificationsView; diff --git a/front/views/settings/NotificationsSettings.tsx b/front/views/settings/NotificationsSettings.tsx new file mode 100644 index 0000000..a8a8b00 --- /dev/null +++ b/front/views/settings/NotificationsSettings.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { Flex } from 'native-base'; +import { translate } from '../../i18n/i18n'; +import ElementList from '../../components/GtkUI/ElementList'; +import useUserSettings from '../../hooks/userSettings'; +import { LoadingView } from '../../components/Loading'; +import { Calendar1, MonitorMobbile, Send2, Warning2 } from 'iconsax-react-native'; + +const NotificationsSettings = () => { + const { settings, updateSettings } = useUserSettings(); + + if (!settings.data) { + return ; + } + return ( + , + title: translate('SettingsNotificationsPushNotifications'), + description: 'Cette notification apparaitra sur votre apparail en pop-up', + data: { + value: settings.data.notifications.pushNotif, + onToggle: () => { + updateSettings({ + notifications: { + pushNotif: !settings.data.notifications.pushNotif, + }, + }); + }, + }, + }, + { + type: 'toggle', + icon: , + title: translate('SettingsNotificationsEmailNotifications'), + description: 'Recevez des mails pour atteindre vos objectifs', + data: { + value: settings.data.notifications.emailNotif, + onToggle: () => { + updateSettings({ + notifications: { + emailNotif: !settings.data.notifications.emailNotif, + }, + }); + }, + }, + }, + { + type: 'toggle', + icon: , + title: translate('SettingsNotificationsTrainingReminder'), + description: 'Un apprentissage régulier est la clé', + data: { + value: settings.data.notifications.trainNotif, + onToggle: () => { + updateSettings({ + notifications: { + trainNotif: !settings.data.notifications.trainNotif, + }, + }); + }, + }, + }, + { + type: 'toggle', + icon: , + title: translate('SettingsNotificationsReleaseAlert'), + description: 'Restez informé de nos mises à jour', + data: { + value: settings.data.notifications.newSongNotif, + onToggle: () => { + updateSettings({ + notifications: { + newSongNotif: !settings.data.notifications.newSongNotif, + }, + }); + }, + }, + }, + ]} + /> + ); +}; + +export default NotificationsSettings; diff --git a/front/views/settings/PreferencesView.tsx b/front/views/settings/PreferencesSettings.tsx similarity index 90% rename from front/views/settings/PreferencesView.tsx rename to front/views/settings/PreferencesSettings.tsx index f2b2abc..c4ed9a4 100644 --- a/front/views/settings/PreferencesView.tsx +++ b/front/views/settings/PreferencesSettings.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useDispatch } from 'react-redux'; -import { Flex } from 'native-base'; +import { Column, Flex } from 'native-base'; import { useLanguage } from '../../state/LanguageSlice'; import { AvailableLanguages, DefaultLanguage, translate } from '../../i18n/i18n'; import { useSelector } from '../../state/Store'; @@ -9,24 +9,13 @@ import ElementList from '../../components/GtkUI/ElementList'; import LocalSettings from '../../models/LocalSettings'; import { Brush2, Colorfilter, LanguageSquare, Rank, Sound } from 'iconsax-react-native'; -const PreferencesView = () => { +const PreferencesSettings = () => { const dispatch = useDispatch(); const language = useSelector((state) => state.language.value); const settings = useSelector((state) => state.settings.local); return ( - + , @@ -93,11 +82,6 @@ const PreferencesView = () => { ]} /> , @@ -114,11 +98,6 @@ const PreferencesView = () => { ]} /> , @@ -153,8 +132,8 @@ const PreferencesView = () => { },*/ ]} /> - + ); }; -export default PreferencesView; +export default PreferencesSettings; diff --git a/front/views/settings/PrivacySettings.tsx b/front/views/settings/PrivacySettings.tsx new file mode 100644 index 0000000..1a576b8 --- /dev/null +++ b/front/views/settings/PrivacySettings.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Flex } from 'native-base'; +import { translate } from '../../i18n/i18n'; +import ElementList from '../../components/GtkUI/ElementList'; +import { useDispatch } from 'react-redux'; +import { RootState, useSelector } from '../../state/Store'; +import { updateSettings } from '../../state/SettingsSlice'; +import useUserSettings from '../../hooks/userSettings'; +import { LoadingView } from '../../components/Loading'; +import { Driver, Like1, Shop } from 'iconsax-react-native'; + +const PrivacySettings = () => { + const dispatch = useDispatch(); + const settings = useSelector((state: RootState) => state.settings.local); + const { settings: userSettings, updateSettings: updateUserSettings } = useUserSettings(); + + if (!userSettings.data) { + return ; + } + return ( + , + title: translate('dataCollection'), + description: + "Acceptez-vous la récupération de vos données pour l'amélioration de Chromacase ?", + data: { + value: settings.dataCollection, + onToggle: () => + dispatch( + updateSettings({ dataCollection: !settings.dataCollection }) + ), + }, + }, + { + type: 'toggle', + icon: , + title: translate('customAds'), + description: 'Afficher les suggestions dans la section des recommandations', + data: { + value: settings.customAds, + onToggle: () => + dispatch(updateSettings({ customAds: !settings.customAds })), + }, + }, + { + type: 'toggle', + icon: , + title: translate('recommendations'), + description: 'Souhaitez-vous recevoir nos conseils et recommandations ?', + data: { + value: userSettings.data.recommendations, + onToggle: () => + updateUserSettings({ + recommendations: !userSettings.data.recommendations, + }), + }, + }, + ]} + /> + ); +}; + +export default PrivacySettings; diff --git a/front/views/settings/PrivacyView.tsx b/front/views/settings/PrivacyView.tsx deleted file mode 100644 index 5a6c89d..0000000 --- a/front/views/settings/PrivacyView.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import { Flex } from 'native-base'; -import { translate } from '../../i18n/i18n'; -import ElementList from '../../components/GtkUI/ElementList'; -import { useDispatch } from 'react-redux'; -import { RootState, useSelector } from '../../state/Store'; -import { updateSettings } from '../../state/SettingsSlice'; -import useUserSettings from '../../hooks/userSettings'; -import { LoadingView } from '../../components/Loading'; -import { Driver, Like1, Shop } from 'iconsax-react-native'; - -const PrivacyView = () => { - const dispatch = useDispatch(); - const settings = useSelector((state: RootState) => state.settings.local); - const { settings: userSettings, updateSettings: updateUserSettings } = useUserSettings(); - - if (!userSettings.data) { - return ; - } - return ( - - , - title: translate('dataCollection'), - description: - "Acceptez-vous la récupération de vos données pour l'amélioration de Chromacase ?", - data: { - value: settings.dataCollection, - onToggle: () => - dispatch( - updateSettings({ dataCollection: !settings.dataCollection }) - ), - }, - }, - { - type: 'toggle', - icon: , - title: translate('customAds'), - description: 'Afficher les suggestions dans la section des recommandations', - data: { - value: settings.customAds, - onToggle: () => - dispatch(updateSettings({ customAds: !settings.customAds })), - }, - }, - { - type: 'toggle', - icon: , - title: translate('recommendations'), - description: 'Souhaitez-vous recevoir nos conseils et recommandations ?', - data: { - value: userSettings.data.recommendations, - onToggle: () => - updateUserSettings({ - recommendations: !userSettings.data.recommendations, - }), - }, - }, - ]} - /> - - ); -}; - -export default PrivacyView; diff --git a/front/views/settings/ProfileView.tsx b/front/views/settings/ProfileView.tsx deleted file mode 100644 index fdf16d5..0000000 --- a/front/views/settings/ProfileView.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import API from '../../API'; -import { useDispatch } from 'react-redux'; -import { unsetAccessToken } from '../../state/UserSlice'; -import React from 'react'; -import { Column, Text, Button, Box, Flex, Center, Heading, Popover, Toast } from 'native-base'; -import TextButton from '../../components/TextButton'; -import { LoadingView } from '../../components/Loading'; -import ElementList from '../../components/GtkUI/ElementList'; -import { translate } from '../../i18n/i18n'; -import { useQuery } from '../../Queries'; -import UserAvatar from '../../components/UserAvatar'; -import * as ImagePicker from 'expo-image-picker'; - -// Too painful to infer the settings-only, typed navigator. Gave up -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const ProfileSettings = ({ navigation }: { navigation: any }) => { - const userQuery = useQuery(API.getUserInfo); - const dispatch = useDispatch(); - - if (!userQuery.data || userQuery.isLoading) { - return ; - } - const user = userQuery.data; - return ( - - -
- -
- { - navigation.navigate('changeEmail'); - }, - }, - }, - { - type: 'text', - title: translate('verified'), - data: { - text: user.emailVerified ? 'verified' : 'not verified', - onPress: user.emailVerified - ? undefined - : () => API.fetch({ route: '/auth/reverify', method: 'PUT' }), - }, - }, - { - type: 'text', - title: translate('avatar'), - data: { - text: translate('changeIt'), - onPress: () => { - ImagePicker.launchImageLibraryAsync({ - mediaTypes: ImagePicker.MediaTypeOptions.Images, - aspect: [1, 1], - quality: 1, - base64: true, - }).then((result) => { - console.log(result); - const image = result.assets?.at(0); - - if (!result.canceled && image) { - API.updateProfileAvatar(image) - .then(() => { - userQuery.refetch(); - Toast.show({ - description: 'Update successful', - }); - }) - .catch((e) => { - console.error(e); - Toast.show({ description: 'Update failed' }); - }); - } - }); - }, - }, - }, - ]} - /> - - Unlink - // : , - }, - { - type: 'text', - title: translate('nbGamesPlayed'), - data: { - text: user.data.gamesPlayed.toString(), - }, - }, - { - type: 'text', - title: 'XP', - description: translate('XPDescription'), - data: { - text: user.data.xp.toString(), - }, - }, - { - type: 'text', - title: translate('userCreatedAt'), - helperText: - 'La date de création est actuellement arbitraire car le serveur ne retourne pas cette information', - data: { - text: user.data.createdAt.toLocaleDateString(), - }, - }, - { - type: 'text', - title: translate('premiumAccount'), - data: { - text: translate(user.premium ? 'yes' : 'no'), - }, - }, - ]} - /> - - Fonctionnalités premium - - {}, - }, - }, - { - type: 'dropdown', - title: 'Thème de piano', - disabled: true, - data: { - value: 'default', - onSelect: () => {}, - options: [ - { - label: 'Default', - value: 'default', - }, - { - label: 'Catpuccino', - value: 'catpuccino', - }, - ], - }, - }, - ]} - /> -
- - - {!user.isGuest && ( - dispatch(unsetAccessToken())} - translate={{ - translationKey: 'signOutBtn', - }} - /> - )} - {user.isGuest && ( - ( - - )} - > - - - - - {translate('Attention')} - - - {translate('YouAreCurrentlyConnectedWithAGuestAccountWarning')} - - - - - - - - - )} - -
- ); -}; - -export default ProfileSettings; diff --git a/front/views/settings/SettingsPremium.tsx b/front/views/settings/SettingsPremium.tsx new file mode 100644 index 0000000..8d89e98 --- /dev/null +++ b/front/views/settings/SettingsPremium.tsx @@ -0,0 +1,73 @@ +import API from '../../API'; +import React from 'react'; +import { Flex } from 'native-base'; +import { LoadingView } from '../../components/Loading'; +import ElementList from '../../components/GtkUI/ElementList'; +import { translate } from '../../i18n/i18n'; +import { useQuery } from '../../Queries'; +import { Designtools, Magicpen, Star1 } from 'iconsax-react-native'; + +// Too painful to infer the settings-only, typed navigator. Gave up +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const PremiumSettings = () => { + const userQuery = useQuery(API.getUserInfo); + + if (!userQuery.data || userQuery.isLoading) { + return ; + } + const user = userQuery.data; + return ( + , + type: 'text', + title: translate('premiumAccount'), + description: + 'Personalisation premium et outils vous permetant de passer au niveau supperieur', + data: { + text: translate(user.premium ? 'yes' : 'no'), + }, + }, + { + icon: , + type: 'toggle', + title: 'Piano Magique', + description: + 'Fait apparaître de la lumière sur le piano pendant les parties', + helperText: + 'Vous devez posséder le module physique lumineux Chromacase pour pouvoir utiliser cette fonctionnalité', + disabled: true, + data: { + value: false, + onToggle: () => {}, + }, + }, + { + icon: , + type: 'dropdown', + title: 'Thème de piano', + description: 'Définissez le theme de votre piano', + disabled: true, + data: { + value: 'default', + onSelect: () => {}, + options: [ + { + label: 'Default', + value: 'default', + }, + { + label: 'Catpuccino', + value: 'catpuccino', + }, + ], + }, + }, + ]} + /> + ); +}; + +export default PremiumSettings; diff --git a/front/views/settings/SettingsPremiumView.tsx b/front/views/settings/SettingsPremiumView.tsx deleted file mode 100644 index 915fce4..0000000 --- a/front/views/settings/SettingsPremiumView.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import API from '../../API'; -import React from 'react'; -import { Flex } from 'native-base'; -import { LoadingView } from '../../components/Loading'; -import ElementList from '../../components/GtkUI/ElementList'; -import { translate } from '../../i18n/i18n'; -import { useQuery } from '../../Queries'; -import { Designtools, Magicpen, Star1 } from 'iconsax-react-native'; - -// Too painful to infer the settings-only, typed navigator. Gave up -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const PremiumSettings = () => { - const userQuery = useQuery(API.getUserInfo); - - if (!userQuery.data || userQuery.isLoading) { - return ; - } - const user = userQuery.data; - return ( - - , - type: 'text', - title: translate('premiumAccount'), - description: - 'Personalisation premium et outils vous permetant de passer au niveau supperieur', - data: { - text: translate(user.premium ? 'yes' : 'no'), - }, - }, - { - icon: , - type: 'toggle', - title: 'Piano Magique', - description: - 'Fait apparaître de la lumière sur le piano pendant les parties', - helperText: - 'Vous devez posséder le module physique lumineux Chromacase pour pouvoir utiliser cette fonctionnalité', - disabled: true, - data: { - value: false, - onToggle: () => {}, - }, - }, - { - icon: , - type: 'dropdown', - title: 'Thème de piano', - description: 'Définissez le theme de votre piano', - disabled: true, - data: { - value: 'default', - onSelect: () => {}, - options: [ - { - label: 'Default', - value: 'default', - }, - { - label: 'Catpuccino', - value: 'catpuccino', - }, - ], - }, - }, - ]} - /> - - ); -}; - -export default PremiumSettings; diff --git a/front/views/settings/SettingsProfileView.tsx b/front/views/settings/SettingsProfile.tsx similarity index 93% rename from front/views/settings/SettingsProfileView.tsx rename to front/views/settings/SettingsProfile.tsx index 263dd25..179c2a0 100644 --- a/front/views/settings/SettingsProfileView.tsx +++ b/front/views/settings/SettingsProfile.tsx @@ -1,6 +1,6 @@ import API from '../../API'; import React from 'react'; -import { Flex, Toast } from 'native-base'; +import { Column, Flex, Toast } from 'native-base'; import { LoadingView } from '../../components/Loading'; import ElementList from '../../components/GtkUI/ElementList'; import { translate } from '../../i18n/i18n'; @@ -9,6 +9,8 @@ import * as ImagePicker from 'expo-image-picker'; import { Google, PasswordCheck, SmsEdit, UserSquare, Verify } from 'iconsax-react-native'; import ChangeEmailForm from '../../components/forms/changeEmailForm'; import ChangePasswordForm from '../../components/forms/changePasswordForm'; +import LogoutButtonCC from '../../components/UI/LogoutButtonCC'; +import Spacer from '../../components/UI/Spacer'; const handleChangeEmail = async (newEmail: string): Promise => { await API.updateUserEmail(newEmail); @@ -30,19 +32,8 @@ const ProfileSettings = () => { } const user = userQuery.data; return ( - + , @@ -147,7 +138,8 @@ const ProfileSettings = () => { }, ]} /> - + + ); }; diff --git a/front/views/settings/SettingsView.tsx b/front/views/settings/SettingsView.tsx index bb587b9..209f06e 100644 --- a/front/views/settings/SettingsView.tsx +++ b/front/views/settings/SettingsView.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Center, Flex, Text } from 'native-base'; -import ProfileSettings from './SettingsProfileView'; -import NotificationsView from './NotificationView'; -import PrivacyView from './PrivacyView'; -import PreferencesView from './PreferencesView'; +import ProfileSettings from './SettingsProfile'; +import NotificationsSettings from './NotificationsSettings'; +import PrivacySettings from './PrivacySettings'; +import PreferencesSettings from './PreferencesSettings'; import { useWindowDimensions } from 'react-native'; import { TabView, @@ -23,9 +23,11 @@ import { FolderCross, } from 'iconsax-react-native'; import { Scene } from 'react-native-tab-view/lib/typescript/src/types'; -import PremiumSettings from './SettingsPremiumView'; +import PremiumSettings from './SettingsPremium'; +import { RouteProps } from '../../Navigation'; +import ScaffoldCC from '../../components/UI/ScaffoldCC'; -export const PianoSettingsView = () => { +export const PianoSettings = () => { return (
Global settings for the virtual piano @@ -36,10 +38,10 @@ export const PianoSettingsView = () => { const renderScene = SceneMap({ profile: ProfileSettings, premium: PremiumSettings, - preferences: PreferencesView, - notifications: NotificationsView, - privacy: PrivacyView, - piano: PianoSettingsView, + preferences: PreferencesSettings, + notifications: NotificationsSettings, + privacy: PrivacySettings, + piano: PianoSettings, }); const getTabData = (key: string) => { @@ -61,9 +63,8 @@ const getTabData = (key: string) => { } }; -const SetttingsNavigator = () => { +const SetttingsNavigator = (props: RouteProps<{}>) => { const layout = useWindowDimensions(); - const [index, setIndex] = React.useState(0); const [routes] = React.useState([ { key: 'profile', title: 'Profile' }, @@ -73,7 +74,6 @@ const SetttingsNavigator = () => { { key: 'privacy', title: 'Privacy' }, { key: 'piano', title: 'Piano' }, ]); - const renderTabBar = ( props: SceneRendererProps & { navigationState: NavigationState } ) => ( @@ -110,8 +110,16 @@ const SetttingsNavigator = () => { ); return ( - + { onIndexChange={setIndex} initialLayout={{ width: layout.width }} /> - + ); }; diff --git a/front/yarn.lock b/front/yarn.lock index 68f46b0..dcd1524 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -8814,6 +8814,11 @@ expo-asset@~8.7.0: path-browserify "^1.0.0" url-parse "^1.5.9" +expo-blur@~12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/expo-blur/-/expo-blur-12.0.1.tgz#7aa4186620359acfa976dda84360070b634ffe3d" + integrity sha512-7oF/xRIFJukM4/qL6ejZ4Z/4YcVExvBPsBrz7rGYz6PtgAkWwYFR62+ExZOzTEG4hgoPPmlnt1ncimsk/MYUgQ== + expo-constants@~14.0.0, expo-constants@~14.0.2: version "14.0.2" resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-14.0.2.tgz#2cb1dec8f41a64c2fc5b4eecaf77d7661cad01cc" @@ -15134,7 +15139,7 @@ prompts@^2.0.1, prompts@^2.2.1, prompts@^2.3.2, prompts@^2.4.0: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -15472,6 +15477,13 @@ react-merge-refs@^1.0.0: resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06" integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ== +react-native-animatable@1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/react-native-animatable/-/react-native-animatable-1.3.3.tgz#a13a4af8258e3bb14d0a9d839917e9bb9274ec8a" + integrity sha512-2ckIxZQAsvWn25Ho+DK3d1mXIgj7tITkrS4pYDvx96WyOttSvzzFeQnM2od0+FUMzILbdHDsDEqZvnz1DYNQ1w== + dependencies: + prop-types "^15.7.2" + react-native-chart-kit@^6.12.0: version "6.12.0" resolved "https://registry.yarnpkg.com/react-native-chart-kit/-/react-native-chart-kit-6.12.0.tgz#187a4987a668a85b7e93588c248ed2c33b3a06f6" @@ -15501,6 +15513,14 @@ react-native-iphone-x-helper@^1.3.1: resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010" integrity sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg== +react-native-modal@^13.0.1: + version "13.0.1" + resolved "https://registry.yarnpkg.com/react-native-modal/-/react-native-modal-13.0.1.tgz#691f1e646abb96fa82c1788bf18a16d585da37cd" + integrity sha512-UB+mjmUtf+miaG/sDhOikRfBOv0gJdBU2ZE1HtFWp6UixW9jCk/bhGdHUgmZljbPpp0RaO/6YiMmQSSK3kkMaw== + dependencies: + prop-types "^15.6.2" + react-native-animatable "1.3.3" + react-native-paper@^4.12.5: version "4.12.5" resolved "https://registry.yarnpkg.com/react-native-paper/-/react-native-paper-4.12.5.tgz#5ea4bbe02d416d17802a199de748700358c11d3a"