diff --git a/front/API.ts b/front/API.ts index 5d770ac..e7ef39f 100644 --- a/front/API.ts +++ b/front/API.ts @@ -10,7 +10,9 @@ import Constants from "expo-constants"; import store from "./state/Store"; import { Platform } from "react-native"; import { en } from "./i18n/Translations"; -import { useQuery, QueryClient } from "react-query"; +import { QueryClient } from "react-query"; +import UserSettings from "./models/UserSettings"; +import { PartialDeep } from "type-fest"; type AuthenticationInput = { username: string; password: string }; type RegistrationInput = AuthenticationInput & { email: string }; @@ -175,31 +177,48 @@ export default class API { createdAt: new Date("2023-04-09T00:00:00.000Z"), avatar: "https://imgs.search.brave.com/RnQpFhmAFvuQsN_xTw7V-CN61VeHDBg2tkEXnKRYHAE/rs:fit:768:512:1/g:ce/aHR0cHM6Ly96b29h/c3Ryby5jb20vd3At/Y29udGVudC91cGxv/YWRzLzIwMjEvMDIv/Q2FzdG9yLTc2OHg1/MTIuanBn", - }, - settings: { - preferences: { - deviceId: 1, - micVolume: 10, - theme: "system", - lang: "fr", - difficulty: "beg", - colorBlind: false, - }, - notifications: { - pushNotif: false, - emailNotif: false, - trainNotif: false, - newSongNotif: false, - }, - privacy: { - dataCollection: true, - customAd: true, - recommendation: true, - }, - }, + } } as User; } + public static async getUserSettings(): Promise { + const settings = await API.fetch({ + route: "/auth/me/settings", + }); + + // this a dummy settings object, we will need to fetch the real one from the API + return { + notifications: { + pushNotif: settings.pushNotification, + emailNotif: settings.emailNotification, + trainNotif: settings.trainingNotification, + newSongNotif: settings.newSongNotification + }, + recommendations: settings.recommendations, + weeklyReport: settings.weeklyReport, + leaderBoard: settings.leaderBoard, + showActivity: settings.showActivity + }; + } + + public static async updateUserSettings(settings: PartialDeep): Promise { + const dto = { + pushNotification: settings.notifications?.pushNotif, + emailNotification: settings.notifications?.emailNotif, + trainingNotification: settings.notifications?.trainNotif, + newSongNotification: settings.notifications?.newSongNotif, + recommendations: settings.recommendations, + weeklyReport: settings.weeklyReport, + leaderBoard: settings.leaderBoard, + showActivity: settings.showActivity, + } + return API.fetch({ + method: 'PATCH', + route: '/auth/me/settings', + body: dto + }); + } + public static async getUserSkills() { return { pedalsCompetency: Math.random() * 100, diff --git a/front/components/Card.tsx b/front/components/Card.tsx index c0ffa08..33bc255 100644 --- a/front/components/Card.tsx +++ b/front/components/Card.tsx @@ -2,7 +2,6 @@ import { useTheme, Box, Pressable } from 'native-base'; import React from 'react'; import { useColorScheme } from 'react-native'; import { useSelector } from 'react-redux'; -import { SettingsState } from '../state/SettingsSlice'; import { RootState } from '../state/Store'; export const CardBorderRadius = 10; @@ -15,7 +14,7 @@ const cardBorder = (theme: ReturnType) => ({ const Card = (props: Parameters[0] & { onPress: () => void }) => { const theme = useTheme(); - const colorScheme: SettingsState['colorScheme'] = useSelector((state: RootState) => state.settings.settings.colorScheme); + const colorScheme = useSelector((state: RootState) => state.settings.local.colorScheme); const systemColorMode = useColorScheme(); return diff --git a/front/components/Loading.tsx b/front/components/Loading.tsx index 5607316..4ee334c 100644 --- a/front/components/Loading.tsx +++ b/front/components/Loading.tsx @@ -1,7 +1,15 @@ import { useTheme } from "native-base"; -import { Spinner } from "native-base"; +import { Center, Spinner } from "native-base"; const LoadingComponent = () => { const theme = useTheme(); return } + +const LoadingView = () => { + return
+ +
+} + export default LoadingComponent; +export { LoadingView } diff --git a/front/components/PartitionVisualizer/PartitionVisualizer.tsx b/front/components/PartitionVisualizer/PartitionVisualizer.tsx index 7528291..ef716cb 100644 --- a/front/components/PartitionVisualizer/PartitionVisualizer.tsx +++ b/front/components/PartitionVisualizer/PartitionVisualizer.tsx @@ -1,7 +1,7 @@ import { useTheme, Box, Center } from "native-base"; import React from "react"; import { useQuery } from "react-query"; -import LoadingComponent from "../Loading"; +import LoadingComponent, { LoadingView } from "../Loading"; import SlideView from "./SlideView"; import API from "../../API"; @@ -13,11 +13,7 @@ const PartitionVisualizer = ({ songId }: PartitionVisualizerProps) => { if (!partitionRessources.data) { - return ( -
- -
- ); + return ; } return ( diff --git a/front/hooks/colorScheme.ts b/front/hooks/colorScheme.ts index ea7eaf9..d8a67f3 100644 --- a/front/hooks/colorScheme.ts +++ b/front/hooks/colorScheme.ts @@ -1,10 +1,9 @@ import { Appearance } from "react-native"; import { useSelector } from "react-redux"; -import { SettingsState } from "../state/SettingsSlice"; import { RootState } from "../state/Store"; const useColorScheme = (): 'light' | 'dark' => { - const colorScheme: SettingsState['colorScheme'] = useSelector((state: RootState) => state.settings.settings.colorScheme); + const colorScheme = useSelector((state: RootState) => state.settings.local.colorScheme); const systemColorScheme = Appearance.getColorScheme(); if (colorScheme == 'system') { diff --git a/front/hooks/userSettings.ts b/front/hooks/userSettings.ts new file mode 100644 index 0000000..8ee998d --- /dev/null +++ b/front/hooks/userSettings.ts @@ -0,0 +1,13 @@ +import { useQuery } from "react-query" +import API from "../API" + +const useUserSettings = () => { + const queryKey = ['settings']; + const settings = useQuery(queryKey, () => API.getUserSettings()) + const updateSettings = (...params: Parameters) => API + .updateUserSettings(...params) + .then(() => settings.refetch()); + return { settings, updateSettings } +} + +export default useUserSettings; \ No newline at end of file diff --git a/front/models/LocalSettings.ts b/front/models/LocalSettings.ts new file mode 100644 index 0000000..137c201 --- /dev/null +++ b/front/models/LocalSettings.ts @@ -0,0 +1,10 @@ +interface LocalSettings { + deviceId: number, + micVolume: number, + colorScheme: 'light' | 'dark' | 'system', + lang: 'fr' | 'en' | 'sp', + difficulty: 'beg' | 'inter' | 'pro', + colorBlind: boolean, + customAds: boolean, + dataCollection: boolean +} \ No newline at end of file diff --git a/front/models/UserSettings.ts b/front/models/UserSettings.ts index dbdd24d..30e8633 100644 --- a/front/models/UserSettings.ts +++ b/front/models/UserSettings.ts @@ -1,23 +1,14 @@ interface UserSettings { - preferences: { - deviceId: number, - micVolume: number, - theme: 'light' | 'dark' | 'system', - lang: 'fr' | 'en' | 'sp', - difficulty: 'beg' | 'inter' | 'pro', - colorBlind: boolean - }, notifications: { pushNotif: boolean, emailNotif: boolean, trainNotif: boolean, newSongNotif: boolean }, - privacy: { - dataCollection: boolean, - customAd: boolean, - recommendation: boolean - } + weeklyReport: boolean, + leaderBoard: boolean, + showActivity: boolean, + recommendations: boolean } export default UserSettings \ No newline at end of file diff --git a/front/state/SettingsSlice.ts b/front/state/SettingsSlice.ts index cab31ae..d5ef39e 100644 --- a/front/state/SettingsSlice.ts +++ b/front/state/SettingsSlice.ts @@ -1,40 +1,22 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -export type SettingsState = { - colorScheme: "dark" | "light" | "system", - enablePushNotifications: boolean, - enableMailNotifications: boolean, - enableLessongsReminders: boolean, - enableReleaseAlerts: boolean, - preferedLevel: 'easy' | 'medium' | 'hard', - colorBlind: boolean, - micLevel: number, - preferedInputName?: string, - dataCollection: boolean, - customAds: boolean, - recommandations: boolean, -} - export const settingsSlice = createSlice({ name: 'settings', initialState: { - settings: { - enablePushNotifications: true, - enableMailNotifications: true, - enableLessongsReminders: true, - enableReleaseAlerts: true, - preferedLevel: 'easy', + local: { + deviceId: 0, + micVolume: 0, + colorScheme: 'system', + lang: 'en', + difficulty: 'beg', colorBlind: false, - micLevel: 50, - colorScheme: "system", - dataCollection: true, customAds: true, - recommandations: true, + dataCollection: true }, }, reducers: { - updateSettings: (state, action: PayloadAction>) => { - state.settings = { ...state.settings, ...action.payload }; + updateSettings: (state, action: PayloadAction>) => { + state.local = { ...state.local, ...action.payload }; } } }); diff --git a/front/views/settings/NotificationView.tsx b/front/views/settings/NotificationView.tsx index e713dae..7b52483 100644 --- a/front/views/settings/NotificationView.tsx +++ b/front/views/settings/NotificationView.tsx @@ -1,17 +1,16 @@ import React from "react"; import { Center, Heading } from "native-base"; import { translate, Translate } from "../../i18n/i18n"; -import { useDispatch } from "react-redux"; -import { RootState, useSelector } from "../../state/Store"; -import { SettingsState, updateSettings } from "../../state/SettingsSlice"; import ElementList from "../../components/GtkUI/ElementList"; +import useUserSettings from "../../hooks/userSettings"; +import { LoadingView } from "../../components/Loading"; -const NotificationsView = ({ navigation }) => { - const dispatch = useDispatch(); - const settings: SettingsState = useSelector( - (state: RootState) => state.settings.settings as SettingsState - ); +const NotificationsView = () => { + const { settings, updateSettings } = useUserSettings(); + if (!settings.data) { + return + } return (
@@ -28,13 +27,11 @@ const NotificationsView = ({ navigation }) => { type: "toggle", title: translate("SettingsNotificationsPushNotifications"), data: { - value: settings.enablePushNotifications, + value: settings.data.notifications.pushNotif, onToggle: () => { - dispatch( - updateSettings({ - enablePushNotifications: !settings.enablePushNotifications, - }) - ); + updateSettings({ + notifications: { pushNotif: !settings.data.notifications.pushNotif }, + }); }, }, }, @@ -42,13 +39,11 @@ const NotificationsView = ({ navigation }) => { type: "toggle", title: translate("SettingsNotificationsEmailNotifications"), data: { - value: settings.enableMailNotifications, + value: settings.data.notifications.emailNotif, onToggle: () => { - dispatch( - updateSettings({ - enableMailNotifications: !settings.enableMailNotifications, - }) - ); + updateSettings({ + notifications: { emailNotif: !settings.data.notifications.emailNotif }, + }); }, }, }, @@ -56,13 +51,11 @@ const NotificationsView = ({ navigation }) => { type: "toggle", title: translate("SettingsNotificationsTrainingReminder"), data: { - value: settings.enableLessongsReminders, + value: settings.data.notifications.trainNotif, onToggle: () => { - dispatch( - updateSettings({ - enableLessongsReminders: !settings.enableLessongsReminders, - }) - ); + updateSettings({ + notifications: { trainNotif: !settings.data.notifications.trainNotif }, + }); }, }, }, @@ -70,13 +63,11 @@ const NotificationsView = ({ navigation }) => { type: "toggle", title: translate("SettingsNotificationsReleaseAlert"), data: { - value: settings.enableReleaseAlerts, + value: settings.data.notifications.newSongNotif, onToggle: () => { - dispatch( - updateSettings({ - enableReleaseAlerts: !settings.enableReleaseAlerts, - }) - ); + updateSettings({ + notifications: { newSongNotif: !settings.data.notifications.newSongNotif }, + }); }, }, }, diff --git a/front/views/settings/PreferencesView.tsx b/front/views/settings/PreferencesView.tsx index 4b63bbd..95b1f38 100644 --- a/front/views/settings/PreferencesView.tsx +++ b/front/views/settings/PreferencesView.tsx @@ -1,33 +1,27 @@ import React from "react"; -import { View } from "react-native"; import { useDispatch } from "react-redux"; import { Center, - Button, - Text, - Switch, - Slider, - Select, Heading, } from "native-base"; import { useLanguage } from "../../state/LanguageSlice"; -import i18n, { +import { AvailableLanguages, DefaultLanguage, translate, Translate, } from "../../i18n/i18n"; import { RootState, useSelector } from "../../state/Store"; -import { SettingsState, updateSettings } from "../../state/SettingsSlice"; +import { updateSettings } from "../../state/SettingsSlice"; import ElementList from "../../components/GtkUI/ElementList"; -const PreferencesView = ({ navigation }) => { +const PreferencesView = () => { const dispatch = useDispatch(); const language: AvailableLanguages = useSelector( (state: RootState) => state.language.value ); const settings = useSelector( - (state: RootState) => state.settings.settings as SettingsState + (state: RootState) => state.settings.local ); return (
@@ -79,15 +73,15 @@ const PreferencesView = ({ navigation }) => { type: "dropdown", title: translate("SettingsPreferencesDifficulty"), data: { - value: settings.preferedLevel, + value: settings.difficulty, defaultValue: "medium", onSelect: (itemValue) => { - dispatch(updateSettings({ preferedLevel: itemValue as any })); + dispatch(updateSettings({ difficulty: itemValue as any })); }, options: [ - { label: translate("easy"), value: "easy" }, - { label: translate("medium"), value: "medium" }, - { label: translate("hard"), value: "hard" }, + { label: translate("easy"), value: "beg" }, + { label: translate("medium"), value: "inter" }, + { label: translate("hard"), value: "pro" }, ], }, }, @@ -123,16 +117,16 @@ const PreferencesView = ({ navigation }) => { type: "range", title: translate("SettingsPreferencesMicVolume"), data: { - value: settings.micLevel, + value: settings.micVolume, min: 0, max: 1000, step: 10, onChange: (value) => { - dispatch(updateSettings({ micLevel: value })); + dispatch(updateSettings({ micVolume: value })); }, }, }, - { + /*{ type: "dropdown", title: translate("SettingsPreferencesDevice"), data: { @@ -147,7 +141,7 @@ const PreferencesView = ({ navigation }) => { { label: "Mic_2", value: "2" }, ], }, - }, + },*/ ]} />
diff --git a/front/views/settings/PrivacyView.tsx b/front/views/settings/PrivacyView.tsx index 87f86f4..e2bfcae 100644 --- a/front/views/settings/PrivacyView.tsx +++ b/front/views/settings/PrivacyView.tsx @@ -4,14 +4,18 @@ import { translate } from "../../i18n/i18n"; import ElementList from "../../components/GtkUI/ElementList"; import { useDispatch } from "react-redux"; import { RootState, useSelector } from "../../state/Store"; -import { SettingsState, updateSettings } from "../../state/SettingsSlice"; +import { updateSettings } from "../../state/SettingsSlice"; +import useUserSettings from "../../hooks/userSettings"; +import { LoadingView } from "../../components/Loading"; const PrivacyView = () => { const dispatch = useDispatch(); - const settings: SettingsState = useSelector( - (state: RootState) => state.settings.settings as SettingsState - ); + const settings = useSelector((state: RootState) => state.settings.local); + const { settings: userSettings, updateSettings: updateUserSettings } = useUserSettings(); + if (!userSettings.data) { + return ; + } return (
{translate("privBtn")} @@ -47,11 +51,9 @@ const PrivacyView = () => { type: "toggle", title: translate("recommendations"), data: { - value: settings.recommandations, + value: userSettings.data.recommendations, onToggle: () => - dispatch( - updateSettings({ recommandations: !settings.recommandations }) - ), + updateUserSettings({ recommendations: !userSettings.data.recommendations }) }, }, ]} diff --git a/front/views/settings/SettingsProfileView.tsx b/front/views/settings/SettingsProfileView.tsx index 1e0698b..9089b0c 100644 --- a/front/views/settings/SettingsProfileView.tsx +++ b/front/views/settings/SettingsProfileView.tsx @@ -1,26 +1,20 @@ import API from "../../API"; import { useDispatch } from "react-redux"; import { unsetAccessToken } from "../../state/UserSlice"; - -import React, { useEffect, useState } from "react"; +import React from "react"; import { Column, Text, Button, - Icon, Box, - IconButton, Flex, - Row, Center, Heading, Avatar, Popover, } from "native-base"; -import { FontAwesome5 } from "@expo/vector-icons"; -import User from "../../models/User"; import TextButton from "../../components/TextButton"; -import LoadingComponent from "../../components/Loading"; +import { LoadingView } from "../../components/Loading"; import ElementList from "../../components/GtkUI/ElementList"; import { translate } from "../../i18n/i18n"; import { useQuery } from "react-query"; @@ -30,26 +24,13 @@ const getInitials = (name: string) => { }; const ProfileSettings = ({ navigation }: { navigation: any }) => { - const userQuery = useQuery(["appSettings", "user"], API.getUserInfo); + const userQuery = useQuery(['user'], () => API.getUserInfo()); const dispatch = useDispatch(); + + if (!userQuery.data || userQuery.isLoading) { + return + } const user = userQuery.data; - - if (userQuery.isError) { - return ( -
- {translate("errorLoadingUser")} -
- ); - } - - if (!userQuery || userQuery.isLoading || !userQuery.data) { - return ( -
- -
- ); - } - return ( { title: "ID", helperText: "This is your unique ID, be proud of it!", data: { - text: user.id, + text: user.id.toString(), }, }, { type: "text", title: translate("nbGamesPlayed"), data: { - text: user.data.gamesPlayed, + text: user.data.gamesPlayed.toString(), }, }, { @@ -123,7 +104,7 @@ const ProfileSettings = ({ navigation }: { navigation: any }) => { title: "XP", description: translate("XPDescription"), data: { - text: user.data.xp, + text: user.data.xp.toString(), }, }, { diff --git a/front/views/settings/SettingsView.tsx b/front/views/settings/SettingsView.tsx index dd7893d..e0f96bb 100644 --- a/front/views/settings/SettingsView.tsx +++ b/front/views/settings/SettingsView.tsx @@ -1,16 +1,11 @@ import React from 'react'; -import { View } from 'react-native'; -import { Center, Button, Text, Switch, Slider, Select, Heading, Box } from "native-base"; +import { Center, Button, Text, Heading, Box } from "native-base"; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { unsetAccessToken } from '../../state/UserSlice'; import { useDispatch } from "react-redux"; -import { RootState, useSelector } from '../../state/Store'; -import { useLanguage } from "../../state/LanguageSlice"; -import { SettingsState, updateSettings } from '../../state/SettingsSlice'; -import { AvailableLanguages, translate, Translate } from "../../i18n/i18n"; -import TextButton from '../../components/TextButton'; +import { translate, Translate } from "../../i18n/i18n"; import createTabRowNavigator from '../../components/navigators/TabRowNavigator'; -import { FontAwesome, MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons'; +import { MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons'; import ChangePasswordForm from '../../components/forms/changePasswordForm'; import ChangeEmailForm from '../../components/forms/changeEmailForm'; import ProfileSettings from './SettingsProfileView'; @@ -19,9 +14,7 @@ import PrivacyView from './PrivacyView'; import PreferencesView from './PreferencesView'; import GuestToUserView from './GuestToUserView'; import { useQuery } from 'react-query'; - -import API, { APIError } from '../../API'; -import User from '../../models/User'; +import API from '../../API'; const SettingsStack = createNativeStackNavigator(); @@ -117,7 +110,7 @@ export const PianoSettingsView = ({navigation}) => { const TabRow = createTabRowNavigator(); const SetttingsNavigator = () => { - const userQuery = useQuery(["appSettings", 'user'], API.getUserInfo); + const userQuery = useQuery(['user'], () => API.getUserInfo()); const user = userQuery.data; if (userQuery.isError) {