Update API and can change spotify account

pull/19/head
Emre KARTAL 1 year ago
parent 85b47bf961
commit a0f7cbf89c

@ -8,7 +8,6 @@
--- ---
  ![Redux](https://img.shields.io/badge/Redux-593D88?style=for-the-badge&logo=redux&logoColor=white)
  ![Docker](https://img.shields.io/badge/Docker-2496ED.svg?style=for-the-badge&logo=Docker&logoColor=white)   ![Docker](https://img.shields.io/badge/Docker-2496ED.svg?style=for-the-badge&logo=Docker&logoColor=white)
  ![React Native](https://img.shields.io/badge/React_Native-20232A?style=for-the-badge&logo=react&logoColor=61DAFB)   ![React Native](https://img.shields.io/badge/React_Native-20232A?style=for-the-badge&logo=react&logoColor=61DAFB)
  ![Spotify Api](https://img.shields.io/badge/Spotify-1ED760?&style=for-the-badge&logo=spotify&logoColor=white)   ![Spotify Api](https://img.shields.io/badge/Spotify-1ED760?&style=for-the-badge&logo=spotify&logoColor=white)
@ -34,9 +33,9 @@
La racine de notre gitlab est composée de deux dossiers essentiels au projet: La racine de notre gitlab est composée de deux dossiers essentiels au projet:
[**src**](src) : **Toute la partie codage de l'application mobile** (contient un dossier API pour l'API FLAD qui effectue les requêtes vers l'API SPOTIFY et la base de données, ainsi qu'un dossier FLAD qui contient toute la partie côté client de l'application) [**src**](src) : **Ensemble du code pour l'application mobile et les services web** (Application React Native, API Express en TypeScript, et messagerie MQTT)
[**doc**](doc) : **Documentation de l'application** (contient les maquettes) [**doc**](doc) : **Documentation de l'application** (Inclut des diagrammes, des maquettes et des images)
## Fonctionnement ## Fonctionnement
@ -62,11 +61,11 @@ Pour la suite, il suffit seulement de vérifier que node.js est à jour et insta
Maintenant vous pouvez à tout moment lancer l'application grâce à la commande : **npx expo start :sunglasses:** Maintenant vous pouvez à tout moment lancer l'application grâce à la commande : **npx expo start :sunglasses:**
<br> <br>
:information_source: *Cliquer sur la touche 'w' si vous voulez le visualiser sur un navigateur (ce que je ne conseille pas) ou installer l'application 'Expo go' de votre téléphone et scanner le QR code proposer pour le visualiser (à noter que l'ordinateur dans lequel il se voit lancer doit être dans le même réseau local que votre téléphone)* :information_source: *N'oubliez pas d'installer 'Expo Go' depuis le store de votre téléphone.*
- ### Comment le lancer à partir de l'iut d'Aubière ? - ### Comment le lancer à partir de l'IUT d'Aubière ?
Cela est un peu plus difficile mais faisable !!! Cela est un peu plus difficile mais faisable !
<br> <br>
Tout d'abord aller dans votre compte scratch : **cd home/scratch/compte** Tout d'abord aller dans votre compte scratch : **cd home/scratch/compte**
@ -89,11 +88,10 @@ Et entrer la commande : **export NODE_OPTIONS=--openssl-legacy-provider**
Maintenant vous pouvez à tout moment lancer l'application grâce à la commande : **npx expo start :sunglasses:** Maintenant vous pouvez à tout moment lancer l'application grâce à la commande : **npx expo start :sunglasses:**
<br> <br>
:information_source: *Cliquer sur la touche 'w' si vous voulez le visualiser sur un navigateur (ce que je ne conseille pas) ou installer l'application 'Expo go' de votre téléphone et scanner le QR code proposer pour le visualiser (à noter que l'ordinateur dans lequel il se voit lancer doit être dans le même réseau local que votre téléphone)*
- ### Comment s'inscrire sur l'application ? - ### Comment s'inscrire sur l'application ?
Tout d'abord, il faut fournir votre *adresse e-mail* et votre *nom Spotify* aux **techniciens de l'application** (voir plus bas). Ils s'occuperont de vous ajouter définitivement à l'application. Une fois que cela est fait, inscrivez-vous via la **page d'inscription** de l'application en cliquant d'abord sur le bouton 'lier mon compte': Tout d'abord, il faut fournir votre *adresse e-mail* et votre *nom Spotify* aux **techniciens de l'application** (voir plus bas). Ils s'occuperont de vous ajouter définitivement à l'application. Une fois que cela est fait, inscrivez-vous via la **page d'inscription** de l'application :
<div align = center> <div align = center>
@ -102,7 +100,7 @@ Tout d'abord, il faut fournir votre *adresse e-mail* et votre *nom Spotify* aux
</div> </div>
Vous serez normalement redirigé sur la page Spotify où vous devrez vous connecter. Une fois connecté, entrez votre nom, votre adresse e-mail et votre mot de passe en tant qu'utilisateur FLAD (n'oubliez pas ces informations car vous en aurez besoin pour vous connecter). Ensuite, cliquez sur le bouton ```suivant``` et bienvenue sur l'application ! Une fois sur la page, saisissez votre nom, votre adresse e-mail, et votre mot de passe en tant qu'utilisateur FLAD (n'oubliez pas ces informations, vous en aurez besoin pour vous connecter). Pour lier votre compte à Spotify, vous serez automatiquement redirigé vers la page de connexion Spotify. Entrez vos identifiants Spotify, puis cliquez sur le bouton ```Suivant``` et bienvenue sur l'application !"
## Visuel de l'Application ## Visuel de l'Application
@ -114,8 +112,13 @@ Vous serez normalement redirigé sur la page Spotify où vous devrez vous connec
:information_source: Lorsque vous entrez dans notre application, la page d'accueil (**home**) vous permet de découvrir les musiques :notes: des utilisateurs autour de vous. Vous pouvez valider une musique soit en cliquant sur le bouton, soit en la glissant vers la droite :point_up_2:. Cette musique sera alors ajoutée à la page **favoris** :heart: et vous pourrez entamer une discussion avec l'utilisateur dans la page **chat** :speech_balloon:. :information_source: Lorsque vous entrez dans notre application, la page d'accueil (**home**) vous permet de découvrir les musiques :notes: des utilisateurs autour de vous. Vous pouvez valider une musique soit en cliquant sur le bouton, soit en la glissant vers la droite :point_up_2:. Cette musique sera alors ajoutée à la page **favoris** :heart: et vous pourrez entamer une discussion avec l'utilisateur dans la page **chat** :speech_balloon:.
<br/> <br/>
Dans la page **settings** ⚙️, vous avez accès à toutes vos informations ```Spotify```, que vous pouvez modifier à votre guise. Toutefois, ces modifications ne seront prises en compte que dans notre application. Vous pouvez également choisir le mode sombre (dark mode) dans les paramètres pour une expérience de navigation plus confortable.
Pour accéder aux détails d'une musique, maintenez votre doigt appuyé sur un Spot ou rendez-vous sur la page des favoris. Vous pourrez écouter la musique :arrow_forward:, obtenir des informations sur l'artiste et la chanson, découvrir des musiques similaires, et même l'ajouter à votre playlist Spotify ou la partager.
<br/> <br/>
`Dans la page **settings** ⚙️, vous avez accès à toutes vos informations ```Spotify```, que vous pouvez modifier à votre guise. Toutefois, ces modifications ne seront prises en compte que dans notre application. Vous pouvez également choisir le mode sombre (dark mode) dans les paramètres pour une expérience de navigation plus confortable.
<br/>
### Voici un petit récapitulatif ### Voici un petit récapitulatif
<div align="center"> <div align="center">
<table> <table>
@ -125,9 +128,9 @@ Dans la page **settings** ⚙️, vous avez accès à toutes vos informations ``
<td align="center"><img src="doc/Images/Like_Img.png" alt="Button 3" width="100" height="100"></td> <td align="center"><img src="doc/Images/Like_Img.png" alt="Button 3" width="100" height="100"></td>
</tr> </tr>
<tr> <tr>
<td align="center">Suprimer de la pile un spot</td> <td align="center">Supprimer de la pile un spot</td>
<td align="center">Ajout Discover (pour l'instant il permet d'ajouter des spot suplémentaires dans la pile pour que vous puissiez vous amusez si il n'y aucun utilisateur à coté de vous)</td> <td align="center">Ajout dans une playlist de votre compte Spotify (créée spécialement par l'application)</td>
<td align="center">Like pour ajouter au favorie</td> <td align="center">Ajouter à vos favoris</td>
</tr> </tr>
</table> </table>
</div> </div>

@ -1,6 +1,5 @@
import IController from './interfaces/IController'; import IController from './interfaces/IController';
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response } from 'express';
import HttpException from '../exception/HttpException';
import axios from 'axios'; import axios from 'axios';
import qs from 'qs'; import qs from 'qs';
@ -98,8 +97,7 @@ class SpotifyController implements IController {
private login = async ( private login = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
try { try {
const redirectResponse = req.query.redirectUrl ? req.query.redirectUrl : req.headers.referer; const redirectResponse = req.query.redirectUrl ? req.query.redirectUrl : req.headers.referer;
@ -112,14 +110,13 @@ class SpotifyController implements IController {
redirect_uri: this.CALLBACK, redirect_uri: this.CALLBACK,
})); }));
} catch (error) { } catch (error) {
next(new HttpException(400, "Cannot connect: " + error.message)); res.status(400).send('Cannot connect: ' + error.message);
} }
}; };
private getRefreshToken = async ( private getRefreshToken = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
const params = req.query.refresh_token; const params = req.query.refresh_token;
if (!req.query.refresh_token) { if (!req.query.refresh_token) {
@ -158,8 +155,7 @@ class SpotifyController implements IController {
private getAccessToken = async ( private getAccessToken = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
let code = req.query.code; let code = req.query.code;
let storedRedirectUri = req.cookies ? req.cookies[this.clientRedirect] : null; let storedRedirectUri = req.cookies ? req.cookies[this.clientRedirect] : null;
@ -192,7 +188,7 @@ class SpotifyController implements IController {
})); }));
} }
} catch (error) { } catch (error) {
next(new HttpException(400, 'Error connection: ' + error.message)); res.status(400).send('Error connection: ' + error.message);
} }
}; };
} }

@ -1,6 +1,5 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import IController from './interfaces/IController'; import IController from './interfaces/IController';
import HttpException from '../exception/HttpException';
import User from '../models/User'; import User from '../models/User';
import UserService from '../services/UserService'; import UserService from '../services/UserService';
import validator from '../middlewares/UserValidation' import validator from '../middlewares/UserValidation'
@ -363,6 +362,38 @@ class UserController implements IController {
*/ */
this.router.put(`${this.path}/email`, authenticator, this.setEmail); this.router.put(`${this.path}/email`, authenticator, this.setEmail);
/**
* @swagger
* /api/user/spotify:
* put:
* summary: Update the spotify account
* description: Update the spotify account of the authenticated user
* tags:
* - User
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* tokenSpotify:
* type: string
* description: Spotify token
* responses:
* 200:
* description: Spotify account updated successfully
* 401:
* description: Unauthorized - Invalid or missing authentication token
* 409:
* description: Conflict - The provided token is already in use by another user
* 500:
* description: Internal Server Error - Spotify account not authorized or not found
*/
this.router.put(`${this.path}/spotify`, authenticator, this.setSpotify);
/** /**
* @swagger * @swagger
* /api/user/image: * /api/user/image:
@ -430,8 +461,7 @@ class UserController implements IController {
private register = async ( private register = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
let access_token; let access_token;
@ -489,36 +519,33 @@ class UserController implements IController {
); );
res.status(201).json({ token }); res.status(201).json({ token });
} catch (error: any) { } catch (error: any) {
next(new HttpException(409, error.message)); res.status(409).json(error.message);
} }
}; };
private login = async ( private login = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
try { try {
const { email, password } = req.body; const { email, password } = req.body;
const token = await this.userService.login(email, password); const token = await this.userService.login(email, password);
res.status(200).json({ token }); res.status(200).json({ token });
} catch (error: any) { } catch (error: any) {
next(new HttpException(400, error.message)); res.status(400).json(error.message)
} }
}; };
private getUser = ( private getUser = (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Response | void => { ): Response | void => {
res.status(200).send({ data: req.user }); res.status(200).send({ data: req.user });
}; };
private getUsers = async ( private getUsers = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
const userIds = req.query.ids as string; const userIds = req.query.ids as string;
@ -538,22 +565,21 @@ class UserController implements IController {
private deleteUser = async ( private deleteUser = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
try { try {
const { _id } = req.user; const { _id } = req.user;
await this.userService.delete(_id); await this.userService.delete(_id);
await this.locationService.delete(_id);
res.status(204).send(); res.status(204).send();
} catch (error: any) { } catch (error: any) {
next(new HttpException(404, error.message)); res.status(404).json(error.message)
} }
}; };
private getUserNext = async ( private getUserNext = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
try { try {
const longitude = Number(req.query.longitude); const longitude = Number(req.query.longitude);
@ -568,14 +594,13 @@ class UserController implements IController {
res.status(201).send(data); res.status(201).send(data);
} }
catch (error: any) { catch (error: any) {
next(new HttpException(400, 'Cannot create get netUser: ' + error.message)); res.status(400).json(error.message)
} }
} }
private deleteMusic = async ( private deleteMusic = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
try { try {
const { _id } = req.user; const { _id } = req.user;
@ -593,14 +618,13 @@ class UserController implements IController {
} }
} catch (error: any) { } catch (error: any) {
next(new HttpException(404, error.message)); res.status(404).json(error.message)
} }
} }
private addMusic = async ( private addMusic = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
try { try {
const { _id } = req.user; const { _id } = req.user;
@ -616,28 +640,26 @@ class UserController implements IController {
await this.userService.addMusic(_id, music); await this.userService.addMusic(_id, music);
res.status(201).send({ music }); res.status(201).send({ music });
} catch (error: any) { } catch (error: any) {
next(new HttpException(400, error.message)); res.status(400).json(error.message)
} }
} }
private getMusics = async ( private getMusics = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
try { try {
const userId: string = req.user.id; const userId: string = req.user.id;
const musics = await this.userService.getMusics(userId); const musics = await this.userService.getMusics(userId);
return res.status(200).json({ musics }); return res.status(200).json({ musics });
} catch (error: any) { } catch (error: any) {
next(new HttpException(400, error.message)); res.status(400).json(error.message)
} }
} }
private setName = async ( private setName = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
try { try {
const { _id } = req.user; const { _id } = req.user;
@ -652,14 +674,13 @@ class UserController implements IController {
res.status(200).json({ message: 'Name updated successfully' }); res.status(200).json({ message: 'Name updated successfully' });
} catch (error: any) { } catch (error: any) {
next(new HttpException(409, error.message)); res.status(409).json(error.message)
} }
} }
private setEmail = async ( private setEmail = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
try { try {
const { _id } = req.user; const { _id } = req.user;
@ -674,14 +695,74 @@ class UserController implements IController {
res.status(200).json({ message: 'Email updated successfully' }); res.status(200).json({ message: 'Email updated successfully' });
} catch (error: any) { } catch (error: any) {
next(new HttpException(409, error.message)); res.status(409).json(error.message)
}
}
private setSpotify = async (
req: Request,
res: Response
): Promise<Response | void> => {
let access_token;
let idAccount: string;
let image: string;
const { _id, idSpotify } = req.user;
const { tokenSpotify } = req.body;
if (!tokenSpotify) {
return res.status(400).json({ error: 'TokenSpotify is missing in the request.' });
}
const apiBaseUrl = process.env.API_BASE_URL || 'http://localhost:8080/api';
const refreshUrl = `${apiBaseUrl}/spotify/refresh?refresh_token=${tokenSpotify}`;
try {
const authOptions = {
method: 'GET',
url: refreshUrl,
json: true
};
const authResponse = await axios(authOptions);
if (authResponse.status === 200) {
access_token = authResponse.data.access_token;
const headers = {
Authorization: `Bearer ${access_token}`,
};
const resp = await axios.get('https://api.spotify.com/v1/me', { headers });
if (resp.status == 200) {
const images = resp.data.images;
idAccount = resp.data.id;
if (idSpotify === idAccount) {
return res.status(400).json({ error: 'idSpotify cannot be the same as idAccount.' });
}
if (images && images.length > 0) {
images.sort((a: any, b: any) => b.height - a.height);
image = images[0].url;
}
else {
const imagePath = './src/assets/images/default_user.png';
const imageBuffer = fs.readFileSync(imagePath);
const base64Image = 'data:image/png;base64,' + base64js.fromByteArray(imageBuffer);
image = base64Image
}
}
}
} catch (error: any) {
console.log(error);
res.status(500).send("Internal Server Error: Unable to authenticate with Spotify");
return;
}
try {
await this.userService.setSpotify(_id, tokenSpotify, idAccount, image);
res.status(200).json({ message: 'Spotify token updated successfully' });
} catch (error: any) {
res.status(409).json(error.message)
} }
} }
private setImage = async ( private setImage = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
try { try {
const { _id } = req.user; const { _id } = req.user;
@ -691,14 +772,13 @@ class UserController implements IController {
res.status(200).json({ message: 'Image updated successfully' }); res.status(200).json({ message: 'Image updated successfully' });
} catch (error: any) { } catch (error: any) {
next(new HttpException(500, error.message)); res.status(500).json(error.message)
} }
} }
private setPassword = async ( private setPassword = async (
req: Request, req: Request,
res: Response, res: Response
next: NextFunction
): Promise<Response | void> => { ): Promise<Response | void> => {
try { try {
const { _id } = req.user; const { _id } = req.user;
@ -708,7 +788,7 @@ class UserController implements IController {
res.status(200).json({ message: 'Password updated successfully' }); res.status(200).json({ message: 'Password updated successfully' });
} catch (error: any) { } catch (error: any) {
next(new HttpException(500, error.message)); res.status(500).json(error.message)
} }
} }
} }

@ -129,6 +129,21 @@ class UserService {
} }
} }
public async setSpotify(userId: string, tokenSpotify: string, idSpotify: string, image: string): Promise<void | Error> {
try {
await this.user.findByIdAndUpdate(
userId,
{
tokenSpotify: tokenSpotify,
idSpotify: idSpotify,
image: image
}
);
} catch (error) {
throw new Error(error.message);
}
}
public async setImage(userId: string, newImage: string): Promise<void | Error> { public async setImage(userId: string, newImage: string): Promise<void | Error> {
try { try {
await this.user.findByIdAndUpdate( await this.user.findByIdAndUpdate(

@ -17,7 +17,7 @@ export default function UserInfoBadge(props: UserInfoProps) {
paddingHorizontal: normalize(10), paddingHorizontal: normalize(10),
backgroundColor: '#3F1DC3', backgroundColor: '#3F1DC3',
alignSelf: 'flex-start', alignSelf: 'flex-start',
borderRadius: 12, borderRadius: 10,
paddingRight: 20, paddingRight: 20,
}, },
section: { section: {

@ -38,6 +38,7 @@
"react-native-screens": "~3.18.0", "react-native-screens": "~3.18.0",
"react-native-svg": "13.4.0", "react-native-svg": "13.4.0",
"react-navigation-shared-element": "^3.1.3", "react-navigation-shared-element": "^3.1.3",
"react-native-web": "~0.18.9",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"redux": "^4.2.1" "redux": "^4.2.1"
}, },

@ -39,7 +39,18 @@ export const register = (registerCredential: RegisterCredentials) => {
dispatch(setErrorSignup("Email non valide !")); dispatch(setErrorSignup("Email non valide !"));
break; break;
case 409: case 409:
dispatch(setErrorSignup("Email, Spotify ou nom déjà utilisé !")); const duplicateFields = error.response.data || [];
let errorMessage = "Email, Spotify ou nom déjà utilisé !";
if (duplicateFields.includes('idSpotify')) {
errorMessage = "Compte Spotify déjà utilisé !";
}
if (duplicateFields.includes('name')) {
errorMessage = "Nom déjà utilisé !";
}
if (duplicateFields.includes('email')) {
errorMessage = "Email déjà utilisé !";
}
dispatch(setErrorSignup(errorMessage));
break; break;
case 500: case 500:
dispatch(setErrorSignup("Compte Spotify non autorisé !")); dispatch(setErrorSignup("Compte Spotify non autorisé !"));

@ -3,6 +3,7 @@ import configs from "../../constants/config";
import { setDarkMode, setErrorUpdateMessage, userLogin } from "../actions/userActions"; import { setDarkMode, setErrorUpdateMessage, userLogin } from "../actions/userActions";
import * as SecureStore from 'expo-secure-store'; import * as SecureStore from 'expo-secure-store';
import { UserMapper } from "../../models/mapper/UserMapper"; import { UserMapper } from "../../models/mapper/UserMapper";
import { MusicServiceProvider } from "../../models/MusicServiceProvider";
export const darkMode = (value: boolean) => { export const darkMode = (value: boolean) => {
//@ts-ignore //@ts-ignore
@ -120,3 +121,38 @@ export const setImage = (image: string) => {
} }
} }
} }
export const setSpotify = (tokenSpotify: string) => {
//@ts-ignore
return async dispatch => {
try {
let token: string | null = await SecureStore.getItemAsync(configs.key);
const headers = {
'Authorization': 'Bearer ' + token
};
await axios.put(configs.API_URL + '/user/spotify', { tokenSpotify }, { headers });
const user = await axios.get(
configs.API_URL + '/user',
{ headers }
)
MusicServiceProvider.initSpotify(user.data.data.tokenSpotify, user.data.data.idSpotify);
dispatch(userLogin(UserMapper.toModel(user.data.data)));
} catch (error: any) {
switch (error.response.status) {
case 400:
dispatch(setErrorUpdateMessage("Ce compte Spotify est déjà associé à votre compte."))
break;
case 409:
dispatch(setErrorUpdateMessage("Compte Spotify déjà utilisé !"))
break;
case 500:
dispatch(setErrorUpdateMessage("Compte Spotify non autorisé !"));
break;
default:
console.error("Error : " + error.message);
break;
}
}
}
}

@ -1,5 +1,5 @@
import { useIsFocused, useNavigation } from "@react-navigation/native"; import { useIsFocused, useNavigation } from "@react-navigation/native";
import { View, Text, Image, StyleSheet, TouchableOpacity, ScrollView, Share, Alert, Linking, FlatList, ActivityIndicator, Platform, SafeAreaView } from "react-native"; import { View, Text, Image, StyleSheet, TouchableOpacity, ScrollView, Share, Alert, Linking, FlatList, ActivityIndicator, Platform } from "react-native";
import Animated, { interpolate, SensorType, useAnimatedSensor, useAnimatedStyle, withSpring } from "react-native-reanimated"; import Animated, { interpolate, SensorType, useAnimatedSensor, useAnimatedStyle, withSpring } from "react-native-reanimated";
import { Audio } from 'expo-av'; import { Audio } from 'expo-av';
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";

@ -5,15 +5,17 @@ import { Svg, Path } from 'react-native-svg';
import Modal from "react-native-modal"; import Modal from "react-native-modal";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import * as AuthSession from 'expo-auth-session';
import normalize from '../components/Normalize'; import normalize from '../components/Normalize';
import * as ImagePicker from 'expo-image-picker'; import * as ImagePicker from 'expo-image-picker';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { colorsDark } from '../constants/colorsDark'; import { colorsDark } from '../constants/colorsDark';
import { colorsLight } from '../constants/colorsLight'; import { colorsLight } from '../constants/colorsLight';
import { deleteUser } from '../redux/thunk/authThunk'; import { deleteUser } from '../redux/thunk/authThunk';
import { setImage, setMail, setName, setPassword } from '../redux/thunk/userThunk'; import { setImage, setMail, setName, setPassword, setSpotify } from '../redux/thunk/userThunk';
import { setErrorUpdate } from '../redux/actions/userActions'; import { setErrorUpdate } from '../redux/actions/userActions';
import * as FileSystem from 'expo-file-system'; import * as FileSystem from 'expo-file-system';
import configs from '../constants/config';
// @ts-ignore // @ts-ignore
const DismissKeyboard = ({ children }) => ( const DismissKeyboard = ({ children }) => (
@ -85,6 +87,31 @@ export default function ProfilScreen() {
return `data:image/jpg;base64,${base64}`; return `data:image/jpg;base64,${base64}`;
}; };
const submitSpotify = async () => {
const token = await getSpotifyToken();
if (token !== undefined) {
//@ts-ignore
dispatch(setSpotify(token));
}
};
const getSpotifyToken = async () => {
try {
const redirectUri = AuthSession.makeRedirectUri();
const result: any = await AuthSession.startAsync({
authUrl: configs.API_URL + '/spotify/exchange?' + 'redirectUrl=' +
encodeURIComponent(redirectUri)
})
const {
refresh_token: refresh_token,
} = result.params
return refresh_token;
} catch (error) {
Alert.alert("Erreur modification", "La connexion à Spotify à échouer.");
return;
}
}
const submitUsername = () => { const submitUsername = () => {
const isUsernameValid = /^\w+$/.test(username); const isUsernameValid = /^\w+$/.test(username);
@ -250,7 +277,7 @@ export default function ProfilScreen() {
backgroundColor: style.Card, backgroundColor: style.Card,
borderRadius: 13, borderRadius: 13,
alignItems: 'flex-start', alignItems: 'flex-start',
marginBottom: normalize(45) marginBottom: normalize(35)
}, },
textOption: { textOption: {
fontSize: normalize(18), fontSize: normalize(18),
@ -271,6 +298,11 @@ export default function ProfilScreen() {
color: '#1c77fb', color: '#1c77fb',
marginLeft: 12 marginLeft: 12
}, },
textOptionSpotify: {
fontSize: normalize(18),
color: '#1DB954',
marginLeft: 12
},
buttonDeleteOption: { buttonDeleteOption: {
backgroundColor: '#DF0404', backgroundColor: '#DF0404',
padding: 5, padding: 5,
@ -309,6 +341,14 @@ export default function ProfilScreen() {
fontSize: normalize(18) fontSize: normalize(18)
}, },
passwordOption: { passwordOption: {
paddingVertical: 9,
paddingLeft: normalize(10),
backgroundColor: style.Card,
borderRadius: 13,
alignItems: 'flex-start',
marginBottom: normalize(30)
},
spotifyOption: {
paddingVertical: 9, paddingVertical: 9,
paddingLeft: normalize(10), paddingLeft: normalize(10),
backgroundColor: style.Card, backgroundColor: style.Card,
@ -316,6 +356,13 @@ export default function ProfilScreen() {
alignItems: 'flex-start', alignItems: 'flex-start',
marginBottom: normalize(45) marginBottom: normalize(45)
}, },
spotifyView: {
backgroundColor: '#1DB954',
padding: 5,
paddingHorizontal: 5,
borderRadius: 10,
marginLeft: 10
},
passwordIcon: { passwordIcon: {
backgroundColor: '#8e8d92', backgroundColor: '#8e8d92',
padding: 5, padding: 5,
@ -323,6 +370,10 @@ export default function ProfilScreen() {
borderRadius: 10, borderRadius: 10,
marginLeft: 10 marginLeft: 10
}, },
spotifyIcon: {
width: 20,
height: 20
},
optionView: { optionView: {
flexDirection: 'row', flexDirection: 'row',
marginTop: 5 marginTop: 5
@ -425,7 +476,7 @@ export default function ProfilScreen() {
<TextInput placeholderTextColor='#828288' value={username} <TextInput placeholderTextColor='#828288' value={username}
onChangeText={setUsername} placeholder={userCurrent.name} style={styles.textInputId} /> onChangeText={setUsername} placeholder={userCurrent.name} style={styles.textInputId} />
{username.length >= 5 && ( {username.length >= 5 && (
<TouchableOpacity onPress={() => submitUsername()}> <TouchableOpacity onPress={submitUsername}>
<Image source={require("../assets/images/confirm_icon.png")} style={{ width: normalize(25), height: normalize(25) }} /> <Image source={require("../assets/images/confirm_icon.png")} style={{ width: normalize(25), height: normalize(25) }} />
</TouchableOpacity> </TouchableOpacity>
)} )}
@ -435,7 +486,7 @@ export default function ProfilScreen() {
<TextInput placeholderTextColor='#828288' value={email} <TextInput placeholderTextColor='#828288' value={email}
onChangeText={setEmail} placeholder={userCurrent.email} style={styles.textInputMail} /> onChangeText={setEmail} placeholder={userCurrent.email} style={styles.textInputMail} />
{email.length >= 7 && ( {email.length >= 7 && (
<TouchableOpacity onPress={() => submitEmail()}> <TouchableOpacity onPress={submitEmail}>
<Image source={require("../assets/images/confirm_icon.png")} style={{ width: normalize(25), height: normalize(25) }} /> <Image source={require("../assets/images/confirm_icon.png")} style={{ width: normalize(25), height: normalize(25) }} />
</TouchableOpacity> </TouchableOpacity>
)} )}
@ -455,13 +506,24 @@ export default function ProfilScreen() {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={styles.spotifyOption}>
<TouchableOpacity style={{ flexDirection: 'row' }} onPress={submitSpotify}>
<View style={styles.spotifyView}>
<Image style={styles.spotifyIcon} source={require("../assets/images/spotify_icon.png")} />
</View>
<View style={styles.optionView}>
<Text style={styles.textOptionSpotify}>Changer de compte Spotify</Text>
</View>
</TouchableOpacity>
</View>
<View style={styles.deleteOption}> <View style={styles.deleteOption}>
<View style={styles.buttonDeleteOption}> <View style={styles.buttonDeleteOption}>
<Svg width="20" height="20" viewBox="0 0 29 29"> <Svg width="20" height="20" viewBox="0 0 29 29">
<Path d="M10.8157 22.6797C10.3586 22.6797 10.0657 22.4101 10.0422 21.9648L9.69067 9.0625C9.67895 8.62891 9.97192 8.34765 10.4407 8.34765C10.8743 8.34765 11.179 8.61719 11.1907 9.05078L11.5657 21.9648C11.5774 22.3984 11.2727 22.6797 10.8157 22.6797ZM14.3899 22.6797C13.9328 22.6797 13.6164 22.3984 13.6164 21.9648V9.0625C13.6164 8.62891 13.9328 8.34765 14.3899 8.34765C14.8469 8.34765 15.175 8.62891 15.175 9.0625V21.9648C15.175 22.3984 14.8469 22.6797 14.3899 22.6797ZM17.9758 22.6797C17.5188 22.6797 17.2141 22.3984 17.2258 21.9648L17.5891 9.0625C17.6008 8.61719 17.9055 8.34765 18.3391 8.34765C18.8078 8.34765 19.1008 8.62891 19.0891 9.0625L18.7375 21.9648C18.7141 22.4101 18.4211 22.6797 17.9758 22.6797ZM9.24536 5.5H11.1086V2.99219C11.1086 2.32422 11.5774 1.89062 12.2805 1.89062H16.4758C17.1789 1.89062 17.6477 2.32422 17.6477 2.99219V5.5H19.5109V2.875C19.5109 1.17578 18.4094 0.144531 16.6047 0.144531H12.1516C10.3469 0.144531 9.24536 1.17578 9.24536 2.875V5.5ZM3.92505 6.4375H24.8664C25.3469 6.4375 25.7336 6.02734 25.7336 5.54687C25.7336 5.06641 25.3469 4.66797 24.8664 4.66797H3.92505C3.4563 4.66797 3.04614 5.06641 3.04614 5.54687C3.04614 6.03906 3.4563 6.4375 3.92505 6.4375ZM9.0227 26.2422H19.7688C21.4445 26.2422 22.5695 25.1523 22.6516 23.4765L23.4719 6.19141H5.30786L6.13989 23.4883C6.22192 25.164 7.32348 26.2422 9.0227 26.2422Z" fill="white" fill-opacity="0.85" /> <Path d="M10.8157 22.6797C10.3586 22.6797 10.0657 22.4101 10.0422 21.9648L9.69067 9.0625C9.67895 8.62891 9.97192 8.34765 10.4407 8.34765C10.8743 8.34765 11.179 8.61719 11.1907 9.05078L11.5657 21.9648C11.5774 22.3984 11.2727 22.6797 10.8157 22.6797ZM14.3899 22.6797C13.9328 22.6797 13.6164 22.3984 13.6164 21.9648V9.0625C13.6164 8.62891 13.9328 8.34765 14.3899 8.34765C14.8469 8.34765 15.175 8.62891 15.175 9.0625V21.9648C15.175 22.3984 14.8469 22.6797 14.3899 22.6797ZM17.9758 22.6797C17.5188 22.6797 17.2141 22.3984 17.2258 21.9648L17.5891 9.0625C17.6008 8.61719 17.9055 8.34765 18.3391 8.34765C18.8078 8.34765 19.1008 8.62891 19.0891 9.0625L18.7375 21.9648C18.7141 22.4101 18.4211 22.6797 17.9758 22.6797ZM9.24536 5.5H11.1086V2.99219C11.1086 2.32422 11.5774 1.89062 12.2805 1.89062H16.4758C17.1789 1.89062 17.6477 2.32422 17.6477 2.99219V5.5H19.5109V2.875C19.5109 1.17578 18.4094 0.144531 16.6047 0.144531H12.1516C10.3469 0.144531 9.24536 1.17578 9.24536 2.875V5.5ZM3.92505 6.4375H24.8664C25.3469 6.4375 25.7336 6.02734 25.7336 5.54687C25.7336 5.06641 25.3469 4.66797 24.8664 4.66797H3.92505C3.4563 4.66797 3.04614 5.06641 3.04614 5.54687C3.04614 6.03906 3.4563 6.4375 3.92505 6.4375ZM9.0227 26.2422H19.7688C21.4445 26.2422 22.5695 25.1523 22.6516 23.4765L23.4719 6.19141H5.30786L6.13989 23.4883C6.22192 25.164 7.32348 26.2422 9.0227 26.2422Z" fill="white" fill-opacity="0.85" />
</Svg> </Svg>
</View> </View>
<TouchableOpacity onPress={() => deleteAccount()}> <TouchableOpacity onPress={deleteAccount}>
<Text style={styles.textDeleteOption}>Supprimer le compte</Text> <Text style={styles.textDeleteOption}>Supprimer le compte</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@ -476,7 +538,7 @@ export default function ProfilScreen() {
<Text style={styles.titlePassword}>Mot de passe</Text> <Text style={styles.titlePassword}>Mot de passe</Text>
<TouchableOpacity <TouchableOpacity
disabled={newPassword.length < 6 || newPassword !== confirmPassword || oldPassword.length < 6} disabled={newPassword.length < 6 || newPassword !== confirmPassword || oldPassword.length < 6}
onPress={() => submitPassword()}> onPress={submitPassword}>
<View> <View>
<Text style={[styles.updateText, { <Text style={[styles.updateText, {
color: newPassword.length >= 6 && newPassword === confirmPassword && oldPassword.length >= 6 ? '#1c77fb' : '#404040', color: newPassword.length >= 6 && newPassword === confirmPassword && oldPassword.length >= 6 ? '#1c77fb' : '#404040',

@ -89,7 +89,6 @@ export default function RegisterScreen() {
encodeURIComponent(redirectUri) encodeURIComponent(redirectUri)
}) })
const { const {
access_token: access_token,
refresh_token: refresh_token, refresh_token: refresh_token,
} = result.params } = result.params
setSpotifyToken(refresh_token) setSpotifyToken(refresh_token)
@ -99,7 +98,6 @@ export default function RegisterScreen() {
} }
} }
return ( return (
<DismissKeyboard> <DismissKeyboard>
<View style={styles.container}> <View style={styles.container}>

@ -90,11 +90,12 @@ export default function SpotScreen() {
}, },
header: { header: {
left: width / 11, left: width / 11,
top: '2.5%', top: '2.2%',
}, },
titleLabel: { titleLabel: {
fontStyle: 'normal', fontStyle: 'normal',
color: "#FFFFFF", color: "#FFFFFF",
marginTop: 2,
fontSize: normalize(40), fontSize: normalize(40),
fontWeight: "800", fontWeight: "800",
}, },

Loading…
Cancel
Save