New API route for changing password and functionality completion of Settings, Profile, Favorite and Detail pages
continuous-integration/drone/push Build is passing Details

pull/19/head
Emre KARTAL 1 year ago
parent 1a0e02bdef
commit f761028031

@ -42,6 +42,8 @@ class UserController implements IController {
this.router.get(`${this.path}/musics`, authenticator, this.getMusics); this.router.get(`${this.path}/musics`, authenticator, this.getMusics);
this.router.put(`${this.path}/name`, authenticator, this.setName); this.router.put(`${this.path}/name`, authenticator, this.setName);
this.router.put(`${this.path}/email`, authenticator, this.setEmail); this.router.put(`${this.path}/email`, authenticator, this.setEmail);
this.router.put(`${this.path}/image`, authenticator, this.setImage);
this.router.put(`${this.path}/password`, authenticator, this.setPassword);
} }
@ -273,6 +275,40 @@ class UserController implements IController {
next(new HttpException(409, error.message)); next(new HttpException(409, error.message));
} }
} }
private setImage = async (
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> => {
try {
const { _id } = req.user;
const { image } = req.body;
await this.userService.setImage(_id, image);
res.status(200).json({ message: 'Image updated successfully' });
} catch (error: any) {
next(new HttpException(500, error.message));
}
}
private setPassword = async (
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> => {
try {
const { _id } = req.user;
const { oldPassword, newPassword } = req.body;
await this.userService.setPassword(_id, oldPassword, newPassword);
res.status(200).json({ message: 'Password updated successfully' });
} catch (error: any) {
next(new HttpException(500, error.message));
}
}
} }
export default UserController; export default UserController;

@ -95,8 +95,7 @@ class UserService {
try { try {
await this.user.findByIdAndUpdate( await this.user.findByIdAndUpdate(
userId, userId,
{ name: newName }, { name: newName }
{ new: true }
); );
} catch (error) { } catch (error) {
throw new Error(error.message); throw new Error(error.message);
@ -107,13 +106,39 @@ class UserService {
try { try {
await this.user.findByIdAndUpdate( await this.user.findByIdAndUpdate(
userId, userId,
{ email: newEmail }, { email: newEmail }
{ new: true }
); );
} catch (error) { } catch (error) {
throw new Error(error.message); throw new Error(error.message);
} }
} }
public async setImage(userId: string, newImage: string): Promise<void | Error> {
try {
await this.user.findByIdAndUpdate(
userId,
{ image: newImage }
);
} catch (error) {
throw new Error(error.message);
}
}
public async setPassword(userId: string, oldPassword: string, newPassword: string): Promise<void | Error> {
try {
const user = await this.user.findById(userId);
if (await user.isValidPassword(oldPassword)) {
user.password = newPassword;
await user.save();
} else {
throw new Error('Old password does not match.');
}
} catch (error) {
throw new Error(error.message);
}
}
} }
export default UserService; export default UserService;

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

@ -4,12 +4,11 @@ import { useSelector } from 'react-redux';
import { colorsDark } from '../constants/colorsDark'; import { colorsDark } from '../constants/colorsDark';
import { colorsLight } from '../constants/colorsLight'; import { colorsLight } from '../constants/colorsLight';
import normalize from './Normalize'; import normalize from './Normalize';
import Music from '../model/Music';
import Artist from '../model/Artist';
type CardMusicProps = { type CardMusicProps = {
image: string; music: Music
title: string;
description: string;
id: string;
} }
export default function CardMusic(props: CardMusicProps) { export default function CardMusic(props: CardMusicProps) {
@ -17,7 +16,6 @@ export default function CardMusic(props: CardMusicProps) {
const isDark = useSelector(state => state.userReducer.dark); const isDark = useSelector(state => state.userReducer.dark);
const style = isDark ? colorsDark : colorsLight; const style = isDark ? colorsDark : colorsLight;
const source = typeof props.image === 'string' ? { uri: props.image } : props.image;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flexDirection: 'row', flexDirection: 'row',
@ -62,11 +60,11 @@ export default function CardMusic(props: CardMusicProps) {
<View style={styles.container}> <View style={styles.container}>
<View style={styles.imageContainer}> <View style={styles.imageContainer}>
<Image source={source} style={styles.image} /> <Image source={{ uri: props.music.cover }} style={styles.image} />
</View> </View>
<View style={styles.textContainer}> <View style={styles.textContainer}>
<Text style={[styles.title]}>{props.title}</Text> <Text style={[styles.title]}>{props.music.name}</Text>
<Text style={[styles.description]}>{props.description}</Text> <Text style={[styles.description]}>{props.music.artists.map((artist: Artist) => artist.name).join(', ')}</Text>
</View> </View>
</View> </View>
); );

@ -1,35 +0,0 @@
import { View, StyleSheet, Text, FlatList } from "react-native";
import { RenderCellProps } from "./littleCard";
interface HorizontalFlatListProps {
children: (props: RenderCellProps) => React.ReactElement
title: string;
data: any[];
}
export const HorizontalFlatList = ({ title, data, children: RenderCell }: HorizontalFlatListProps) => {
return (
<View style={styles.similarSection}>
<Text style={styles.similarTitle} >{title}</Text>
<FlatList
showsHorizontalScrollIndicator={false}
data={data}
horizontal={true}
keyExtractor={item => item.id}
renderItem={({ item }) => RenderCell(item)} /></View>
);
};
const styles = StyleSheet.create({
similarSection: {
paddingTop: 16
},
similarTitle: {
color: "#FFF",
paddingLeft: 8,
fontSize: 24,
fontWeight: "600",
paddingBottom: 16
}
});

@ -0,0 +1,36 @@
import { View, Text, StyleSheet, Image } from 'react-native';
import Music from '../model/Music';
import normalize from './Normalize';
export interface RenderCellProps {
music : Music;
}
export const SimilarMusic = (props: RenderCellProps) => {
return (
<View style={styles.similarContainer}>
<Image source={{ uri: props.music.cover }} style={styles.similarPoster}></Image>
<Text numberOfLines={1} style={styles.similarTitle}>{props.music.name}
</Text>
</View>
)
}
const styles = StyleSheet.create({
similarContainer: {
marginHorizontal: normalize(7)
},
similarTitle: {
color: "#DADADA",
paddingTop: 5,
paddingLeft: 5,
fontWeight: "600",
maxWidth: normalize(130),
fontSize: normalize(14)
},
similarPoster: {
height: normalize(130),
width: normalize(130),
borderRadius: 12
}
})

@ -1,31 +0,0 @@
import { View, Text, StyleSheet, Image } from 'react-native';
export interface RenderCellProps {
data : any;
}
export const LittleCard = (props: RenderCellProps) => {
return (
<View style={styles.similarContainer}>
<Image source={{ uri: props.data.cover }} style={styles.similarPoster}></Image>
<Text numberOfLines={2} style={styles.similarTitleFilm}>{props.data.name}
</Text>
</View>
)
}
const styles = StyleSheet.create({
similarContainer: {
marginHorizontal: 7
},
similarTitleFilm: {
color: "#DADADA",
paddingTop: 5,
fontWeight: "300"
},
similarPoster: {
height: 160,
width: 160,
borderRadius: 16
}
})

@ -1,13 +1,11 @@
export default class Artist { export default class Artist {
private _id: string; private _id: string;
private _name: string; private _name: string;
private _image: string;
private _url: string; private _url: string;
constructor(id: string, name: string, image: string, url: string) { constructor(id: string, name: string, url: string) {
this._id = id; this._id = id;
this._name = name; this._name = name;
this._image = image;
this._url = url; this._url = url;
} }
@ -27,14 +25,6 @@ export default class Artist {
this._name = value; this._name = value;
} }
get image(): string {
return this._image;
}
set image(value: string) {
this._image = value;
}
get url(): string { get url(): string {
return this._url; return this._url;
} }

@ -6,6 +6,7 @@ export default class Music {
private _url: string; private _url: string;
private _artists: Artist[]; private _artists: Artist[];
private _cover: string; private _cover: string;
private _littleCover: string;
private _date: number; private _date: number;
private _duration: number; private _duration: number;
private _explicit: boolean = false; private _explicit: boolean = false;
@ -17,6 +18,7 @@ export default class Music {
url: string, url: string,
artists: Artist[], artists: Artist[],
cover: string, cover: string,
littleCover: string,
date: number, date: number,
duration: number, duration: number,
explicit: boolean, explicit: boolean,
@ -27,6 +29,7 @@ export default class Music {
this._url = url; this._url = url;
this._artists = artists; this._artists = artists;
this._cover = cover; this._cover = cover;
this._littleCover = littleCover;
this._date = date; this._date = date;
this._duration = duration; this._duration = duration;
this._explicit = explicit; this._explicit = explicit;
@ -73,6 +76,14 @@ export default class Music {
this._cover = value; this._cover = value;
} }
get littleCover(): string {
return this._littleCover;
}
set littleCover(value: string) {
this._littleCover = value;
}
get date(): number { get date(): number {
return this._date; return this._date;
} }

@ -2,6 +2,6 @@ import Artist from "../Artist";
export default class ArtistMapper { export default class ArtistMapper {
static toModel(artist: any): Artist { static toModel(artist: any): Artist {
return new Artist(artist.id, artist.name, (artist?.images?.[0]?.url ?? ""), artist.external_urls.spotify); return new Artist(artist.id, artist.name, artist.external_urls.spotify);
} }
} }

@ -4,12 +4,14 @@ import ArtistMapper from "./ArtistMapper";
export default class MusicMapper { export default class MusicMapper {
static toModel(music: any): Music { static toModel(music: any): Music {
const artists = music.artists.map((artist: any) => ArtistMapper.toModel(artist)); const artists = music.artists.map((artist: any) => ArtistMapper.toModel(artist));
const last = music.album.images.length - 1;
return new Music( return new Music(
music.id, music.id,
music.name, music.name,
music.external_urls.spotify, music.external_urls.spotify,
artists, artists,
music.album.images[0].url, music.album.images[0].url,
music.album.images[last].url,
music.album.release_date.split('-')[0], music.album.release_date.split('-')[0],
music.duration_ms / 1000, music.duration_ms / 1000,
music.explicit, music.explicit,

@ -18,6 +18,7 @@
"expo": "~47.0.12", "expo": "~47.0.12",
"expo-auth-session": "~3.8.0", "expo-auth-session": "~3.8.0",
"expo-av": "~13.0.3", "expo-av": "~13.0.3",
"expo-blur": "~12.0.1",
"expo-cli": "^6.3.10", "expo-cli": "^6.3.10",
"expo-haptics": "~12.0.1", "expo-haptics": "~12.0.1",
"expo-image-picker": "~14.0.2", "expo-image-picker": "~14.0.2",

@ -1,5 +1,5 @@
import Music from "../../models/Music"; import Music from "../../model/Music";
import { Spot } from "../../models/Spot"; import { Spot } from "../../model/Spot";
import { favoritesTypes } from "../types/favoritesTypes"; import { favoritesTypes } from "../types/favoritesTypes";
import { spotifyTypes } from "../types/spotifyTypes"; import { spotifyTypes } from "../types/spotifyTypes";
@ -16,10 +16,3 @@ export const setFavoriteMusic = (spots: Spot[]) => {
payload: spots, payload: spots,
}; };
} }
export const addFavoritesMusic = (music: Music) => {
return {
type: favoritesTypes.ADD_FAVORITE_MUSICS,
payload: music,
};
}

@ -1,5 +1,4 @@
import Music from "../../models/Music"; import { Spot } from "../../model/Spot";
import { Spot } from "../../models/Spot";
import { spotTypes } from "../types/spotTypes"; import { spotTypes } from "../types/spotTypes";
export const setSpotList = (spotList: Spot[]) => { export const setSpotList = (spotList: Spot[]) => {

@ -1,4 +1,4 @@
import { User } from "../../models/User"; import { User } from "../../model/User";
import { userTypes } from "../types/userTypes"; import { userTypes } from "../types/userTypes";
export interface LoginCredentials { export interface LoginCredentials {

@ -1,4 +1,4 @@
import { Spot } from "../../models/Spot"; import { Spot } from "../../model/Spot";
import { favoritesTypes } from "../types/favoritesTypes"; import { favoritesTypes } from "../types/favoritesTypes";
import { spotifyTypes } from "../types/spotifyTypes"; import { spotifyTypes } from "../types/spotifyTypes";
import { spotTypes } from "../types/spotTypes"; import { spotTypes } from "../types/spotTypes";
@ -14,10 +14,8 @@ const appReducer = (state = initialState, action: any) => {
switch (action.type) { switch (action.type) {
case favoritesTypes.GET_FAVORITE_MUSICS: case favoritesTypes.GET_FAVORITE_MUSICS:
return { ...state, favoriteMusic: action.payload }; return { ...state, favoriteMusic: action.payload };
case favoritesTypes.REMOVE_FAVORITE_MUSICS:
return { ...state, favoriteMusic: state.favoriteMusic };
case spotTypes.FETCH_SPOT: case spotTypes.FETCH_SPOT:
const uniqueSpots = action.payload.filter((spot) => { const uniqueSpots = action.payload.filter((spot: Spot) => {
return !state.spot.some((s) => s.userSpotifyId === spot.userSpotifyId && s.music.id === spot.music.id); return !state.spot.some((s) => s.userSpotifyId === spot.userSpotifyId && s.music.id === spot.music.id);
}); });
const updatedSpotList = [...uniqueSpots, ...state.spot]; const updatedSpotList = [...uniqueSpots, ...state.spot];

@ -1,5 +1,5 @@
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import { User } from "../../models/User"; import { User } from "../../model/User";
import { userTypes } from "../types/userTypes"; import { userTypes } from "../types/userTypes";
const initialState = { const initialState = {

@ -1,10 +1,6 @@
import { configureStore } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit'
import appReducer from './reducers/appReducer'; import appReducer from './reducers/appReducer';
import userReducer from './reducers/userReducer'; import userReducer from './reducers/userReducer';
import { spotTypes } from './types/spotTypes';
import { userTypes } from './types/userTypes';
import { spotifyTypes } from './types/spotifyTypes';
import { favoritesTypes } from './types/favoritesTypes';
// Reference here all your application reducers // Reference here all your application reducers
const reducer = { const reducer = {
@ -15,12 +11,9 @@ const reducer = {
const store = configureStore({ const store = configureStore({
// @ts-ignore // @ts-ignore
reducer: reducer, reducer: reducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware({ middleware: (getDefaultMiddleware) =>
serializableCheck: { getDefaultMiddleware({
ignoredActions: [spotTypes.FETCH_SPOT, spotifyTypes.GET_USER_CURRENT_MUSIC, favoritesTypes.ADD_FAVORITE_MUSICS, favoritesTypes.REMOVE_FAVORITE_MUSICS, spotTypes.REMOVE_SPOT, userTypes.LOGIN], serializableCheck: false,
ignoredActionPaths: ['appReducer'],
ignoredPaths: ['appReducer', 'userReducer']
}
}) })
},); },);

@ -1,11 +1,11 @@
import axios from "axios"; import axios from "axios";
import * as SecureStore from 'expo-secure-store'; import * as SecureStore from 'expo-secure-store';
import { Spot } from "../../models/Spot"; import { Spot } from "../../model/Spot";
import configs from "../../constants/config"; import configs from "../../constants/config";
import { MusicServiceProvider } from "../../models/MusicServiceProvider"; import { MusicServiceProvider } from "../../model/MusicServiceProvider";
import { setFavoriteMusic, setUserCurrentMusic } from "../actions/appActions"; import { setFavoriteMusic, setUserCurrentMusic } from "../actions/appActions";
import { setAccessError, setErrorEmptyMusic } from "../actions/userActions"; import { setAccessError, setErrorEmptyMusic } from "../actions/userActions";
import { SpotMapper } from "../../models/mapper/SpotMapper"; import { SpotMapper } from "../../model/mapper/SpotMapper";
export const getUserCurrentMusic = () => { export const getUserCurrentMusic = () => {
//@ts-ignore //@ts-ignore
@ -26,12 +26,12 @@ export const getUserCurrentMusic = () => {
const music = await MusicServiceProvider.musicService.getMusicById(idTrack); const music = await MusicServiceProvider.musicService.getMusicById(idTrack);
dispatch(setUserCurrentMusic(music)) dispatch(setUserCurrentMusic(music))
} catch (error: any) { } catch (error: any) {
console.error("Error retrieving music currently listened : " + error);
switch (error.response.status) { switch (error.response.status) {
case 403: case 403:
dispatch(setAccessError(true)); dispatch(setAccessError(true));
break; break;
default: default:
console.error("Error retrieving music currently listened : " + error);
dispatch(setAccessError(true)); dispatch(setAccessError(true));
break; break;
} }

@ -2,8 +2,8 @@ import axios from "axios";
import configs from "../../constants/config"; import configs from "../../constants/config";
import { LoginCredentials, RegisterCredentials, restoreToken, userLogin, userLogout, setErrorLogin, setErrorSignup, setErrorNetwork } from "../actions/userActions"; import { LoginCredentials, RegisterCredentials, restoreToken, userLogin, userLogout, setErrorLogin, setErrorSignup, setErrorNetwork } 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 "../../model/mapper/UserMapper";
import { MusicServiceProvider } from "../../models/MusicServiceProvider"; import { MusicServiceProvider } from "../../model/MusicServiceProvider";
const keyRemember = 'rememberUser'; const keyRemember = 'rememberUser';
@ -34,7 +34,6 @@ export const register = (resgisterCredential: RegisterCredentials) => {
MusicServiceProvider.initSpotify(user.data.data.tokenSpotify, user.data.data.idSpotify); MusicServiceProvider.initSpotify(user.data.data.tokenSpotify, user.data.data.idSpotify);
dispatch(userLogin(UserMapper.toModel(user.data.data))); dispatch(userLogin(UserMapper.toModel(user.data.data)));
} catch (error: any) { } catch (error: any) {
console.error("Error : " + error.message);
switch (error.response.status) { switch (error.response.status) {
case 400: case 400:
dispatch(setErrorSignup("Email non valide !")); dispatch(setErrorSignup("Email non valide !"));
@ -46,6 +45,7 @@ export const register = (resgisterCredential: RegisterCredentials) => {
dispatch(setErrorSignup("Compte Spotify non autorisé !")); dispatch(setErrorSignup("Compte Spotify non autorisé !"));
break; break;
default: default:
console.error("Error : " + error.message);
dispatch(setErrorSignup("Erreur lors de l'inscription !")); dispatch(setErrorSignup("Erreur lors de l'inscription !"));
break; break;
} }
@ -86,12 +86,12 @@ export const login = (loginCredential: LoginCredentials, remember: boolean) => {
MusicServiceProvider.initSpotify(user.data.data.tokenSpotify, user.data.data.idSpotify); MusicServiceProvider.initSpotify(user.data.data.tokenSpotify, user.data.data.idSpotify);
dispatch(userLogin(UserMapper.toModel(user.data.data))); dispatch(userLogin(UserMapper.toModel(user.data.data)));
} catch (error: any) { } catch (error: any) {
console.error("Error : " + error.message);
switch (error.response.status) { switch (error.response.status) {
case 400: case 400:
dispatch(setErrorLogin(true)); dispatch(setErrorLogin(true));
break; break;
default: default:
console.error("Error : " + error.message);
dispatch(setErrorNetwork(true)); dispatch(setErrorNetwork(true));
break; break;
} }

@ -2,7 +2,7 @@ import axios from "axios";
import configs from "../../constants/config"; import configs from "../../constants/config";
import { setDarkMode, setErrorNetwork, setErrorUpdateMessage, userLogin } from "../actions/userActions"; import { setDarkMode, setErrorNetwork, 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 "../../model/mapper/UserMapper";
export const darkMode = (value: boolean) => { export const darkMode = (value: boolean) => {
//@ts-ignore //@ts-ignore
@ -28,13 +28,12 @@ export const setName = (name: string) => {
dispatch(userLogin(UserMapper.toModel(user.data.data))); dispatch(userLogin(UserMapper.toModel(user.data.data)));
} catch (error: any) { } catch (error: any) {
console.error("Error : " + error.message);
switch (error.response.status) { switch (error.response.status) {
case 409: case 409:
dispatch(setErrorUpdateMessage("Nom déjà utilisé.")) dispatch(setErrorUpdateMessage("Nom déjà utilisé."))
break; break;
default: default:
dispatch(setErrorNetwork(true)); console.error("Error : " + error.message);
break; break;
} }
} }
@ -58,13 +57,64 @@ export const setMail = (email: string) => {
) )
dispatch(userLogin(UserMapper.toModel(user.data.data))); dispatch(userLogin(UserMapper.toModel(user.data.data)));
} catch (error: any) { } catch (error: any) {
console.error("Error : " + error.message);
switch (error.response.status) { switch (error.response.status) {
case 409: case 409:
dispatch(setErrorUpdateMessage("Email déjà utilisé.")) dispatch(setErrorUpdateMessage("Email déjà utilisé."))
break; break;
default: default:
dispatch(setErrorNetwork(true)); console.error("Error : " + error.message);
break;
}
}
}
}
export const setPassword = (oldPassword: string, newPassword: 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/password', { oldPassword, newPassword }, { headers });
} catch (error: any) {
switch (error.response.status) {
case 500:
dispatch(setErrorUpdateMessage("Mot de passe incorrect."))
break;
default:
console.error("Error : " + error.message);
break;
}
}
}
}
export const setImage = (image: 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/image', { image }, { headers });
const user = await axios.get(
configs.API_URL + '/user',
{ headers }
)
dispatch(userLogin(UserMapper.toModel(user.data.data)));
} catch (error: any) {
switch (error.response.status) {
case 413:
dispatch(setErrorUpdateMessage("Taille de l'image trop grande :\n" + image.length / 1000 + " Ko. Max 100 Ko."))
break;
default:
console.error("Error : " + error.message);
break; break;
} }
} }

@ -1,5 +1,4 @@
export const favoritesTypes = { export const favoritesTypes = {
GET_FAVORITE_MUSICS: 'GET_FAVORITE_MUSICS', GET_FAVORITE_MUSICS: 'GET_FAVORITE_MUSICS',
ADD_FAVORITE_MUSICS: 'ADD_FAVORITE_MUSICS', ADD_FAVORITE_MUSICS: 'ADD_FAVORITE_MUSICS',
REMOVE_FAVORITE_MUSICS: 'REMOVE_FAVORITE_MUSICS',
} }

@ -1,3 +0,0 @@
export const playlistTypes = {
SAVE_IN_FLAD_PLAYLIST: 'SAVE_IN_FLAD_PLAYLIST',
}

@ -5,6 +5,7 @@ import { colorsDark } from '../constants/colorsDark';
import { colorsLight } from '../constants/colorsLight'; import { colorsLight } from '../constants/colorsLight';
import Friend from "../components/FriendComponent"; import Friend from "../components/FriendComponent";
import normalize from '../components/Normalize'; import normalize from '../components/Normalize';
import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function ConversationScreen() { export default function ConversationScreen() {
@ -22,10 +23,13 @@ export default function ConversationScreen() {
const style = isDark ? colorsDark : colorsLight; const style = isDark ? colorsDark : colorsLight;
const insets = useSafeAreaInsets();
const styles = StyleSheet.create({ const styles = StyleSheet.create({
mainSafeArea: { mainSafeArea: {
flex: 1, flex: 1,
backgroundColor: style.body, backgroundColor: style.body,
paddingTop: insets.top
}, },
titleContainer: { titleContainer: {
marginTop: 10, marginTop: 10,

@ -1,75 +1,101 @@
import { useNavigation } from "@react-navigation/native"; import { useIsFocused, useNavigation } from "@react-navigation/native";
import { View, Text, Image, StyleSheet, TouchableOpacity, ScrollView, Pressable, Share, Alert } from "react-native"; import { View, Text, Image, StyleSheet, TouchableOpacity, ScrollView, Share, Alert, SafeAreaView, Linking, FlatList, ActivityIndicator } 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";
import normalize from '../components/Normalize'; import normalize from '../components/Normalize';
import Music from "../models/Music"; import Music from "../model/Music";
import { LinearGradient } from "expo-linear-gradient"; import { LinearGradient } from "expo-linear-gradient";
import { Feather as Icon } from "@expo/vector-icons"; import { MusicServiceProvider } from "../model/MusicServiceProvider";
import { MusicServiceProvider } from "../models/MusicServiceProvider"; import { SimilarMusic } from "../components/SimilarMusicComponent";
import { HorizontalFlatList } from "../components/HorizontalFlatList"; import { useSafeAreaInsets } from "react-native-safe-area-context";
import { LittleCard } from "../components/littleCard"; import Artist from "../model/Artist";
import { BlurView } from 'expo-blur';
const halfPi = Math.PI / 2; const halfPi = Math.PI / 2;
//@ts-ignore //@ts-ignore
export default function DetailScreen({ route }) { export default function DetailScreen({ route }) {
const music: Music = route.params.music; const item: Music = route.params.music;
const [currentspot] = useState(music);
const [simularMusic, setSimularMusic] = useState<Music[]>([]); const [simularMusic, setSimularMusic] = useState<Music[]>([]);
const [artistImage, setArtistImage] = useState<string | null>(null);
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
const [sound, setSound] = useState(null); const [addedToPlaylist, setAddedToPlaylist] = useState(false);
const [sound, setSound] = useState<Audio.Sound | null>();
const [loading, setLoading] = useState(true);
const navigator = useNavigation(); const navigator = useNavigation();
useEffect(() => { useEffect(() => {
getSimilarTrack(); getSimilarTrack();
getArtistImage();
if (item.trackPreviewUrl) {
loadMusic();
}
return () => {
if (sound) {
sound.unloadAsync();
}
};
}, []); }, []);
const getSimilarTrack = async () => {
const simularMusic = await MusicServiceProvider.musicService.getSimilarTracks(currentspot.id); const isFocused = useIsFocused();
setSimularMusic(simularMusic);
useEffect(() => {
if (!isFocused && sound) {
sound.stopAsync();
setIsPlaying(false);
} }
}, [isFocused]);
const handlePlaySound = async () => { const loadMusic = async () => {
if (sound === null) { const { sound } = await Audio.Sound.createAsync(
const { sound: newSound } = await Audio.Sound.createAsync( { uri: item.trackPreviewUrl },
{ uri: music.trackPreviewUrl }, { shouldPlay: isPlaying },
{ shouldPlay: true } onPlaybackStatusUpdate
); );
//setSound(newSound); setSound(sound);
setIsPlaying(true); };
} else { const getArtistImage = async () => {
setIsPlaying(true); const image = await MusicServiceProvider.musicService.getImageArtistWithId(item.artists[0].id);
//@ts-ignore setArtistImage(image);
await sound.playAsync();
}
}; };
const handleStopSound = async () => { const onPlaybackStatusUpdate = (status: any) => {
if (sound !== null) { if (status.didJustFinish) {
setIsPlaying(false); setIsPlaying(false);
}
};
//@ts-ignore const play = async () => {
await sound.stopAsync(); if (sound) {
if (isPlaying) {
await sound.pauseAsync();
} else {
await sound.replayAsync();
await sound.playAsync();
}
setIsPlaying(!isPlaying);
} }
}; };
useEffect(() => {
return sound ? () => { const getSimilarTrack = async () => {
console.log('Unloading Sound'); try {
//@ts-ignore const simularMusic = await MusicServiceProvider.musicService.getSimilarTracks(item.id);
sound.unloadAsync(); setSimularMusic(simularMusic);
} finally {
setLoading(false);
}
} }
: undefined;
}, [sound]);
const onShare = async () => { const onShare = async () => {
try { try {
const result = await Share.share({ await Share.share({
message: message:
music.url, item.url,
}); });
} catch (error: any) { } catch (error: any) {
Alert.alert(error.message); Alert.alert(error.message);
@ -77,11 +103,12 @@ export default function DetailScreen({ route }) {
}; };
const addToPlaylist = async () => { const addToPlaylist = async () => {
MusicServiceProvider.musicService.addToPlaylist(music.id); MusicServiceProvider.musicService.addToPlaylist(item.id);
setAddedToPlaylist(true);
}; };
const sensor = useAnimatedSensor(SensorType.ROTATION); const sensor = useAnimatedSensor(SensorType.ROTATION);
const styleAniamatedImage = useAnimatedStyle(() => { const styleAnimatedImage = useAnimatedStyle(() => {
const { pitch, roll } = sensor.sensor.value; const { pitch, roll } = sensor.sensor.value;
const verticalAxis = interpolate( const verticalAxis = interpolate(
pitch, pitch,
@ -99,127 +126,254 @@ export default function DetailScreen({ route }) {
}; };
}) })
const insets = useSafeAreaInsets();
const styles = StyleSheet.create({
mainSafeArea: {
height: '100%',
width: '100%',
paddingTop: insets.top
},
backgroundSection: {
height: "100%",
width: "100%",
position: "absolute"
},
back_drop: {
height: "100%",
width: '100%',
position: "absolute",
},
gradientFade: {
height: "100%",
},
card: {
alignItems: 'center'
},
cardCover: {
width: normalize(390),
height: normalize(390),
borderRadius: 16,
resizeMode: 'stretch'
},
section1: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
marginHorizontal: "10%",
marginBottom: "4%",
marginTop: "5%"
},
section2: {
flex: 1
},
section3: {
flexDirection: "row",
},
similarTitle: {
color: "#FFF",
paddingLeft: "8%",
fontSize: normalize(28),
fontWeight: "600",
paddingBottom: normalize(15)
},
title: {
maxWidth: "90%",
fontSize: normalize(30),
fontWeight: "bold",
color: "white"
},
playButton: {
height: normalize(60),
width: normalize(60),
backgroundColor: "#E70E0E",
borderRadius: 30
},
imagePlayButton: {
width: normalize(40),
height: normalize(40)
},
bodyPlayButton: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
artist: {
maxWidth: "40%",
fontWeight: "bold",
color: "white",
fontSize: normalize(17),
paddingLeft: normalize(5)
},
date: {
fontWeight: "400",
color: "white",
fontSize: normalize(17)
},
buttonArtist: {
flexDirection: "row",
alignItems: "center",
},
saveButton: {
backgroundColor: '#3F3F3F',
width: normalize(180),
height: normalize(50),
padding: 10,
borderRadius: 8,
marginRight: normalize(10),
flexDirection: "row",
alignItems: "center"
},
shareButton: {
backgroundColor: '#3F3F3F',
width: normalize(180),
height: normalize(50),
marginLeft: normalize(10),
padding: 10,
borderRadius: 8,
flexDirection: "row",
alignItems: "center"
},
saveIcon: {
width: normalize(14.7),
height: normalize(21),
marginLeft: normalize(9)
},
shareIcon: {
width: normalize(25),
height: normalize(25),
marginBottom: normalize(5)
},
saveText: {
fontSize: normalize(11),
paddingLeft: normalize(9),
color: "white",
fontWeight: "bold"
},
shareText: {
fontSize: normalize(11),
paddingLeft: normalize(5),
color: "white",
fontWeight: "bold"
},
explicitImage: {
marginLeft: normalize(5),
width: normalize(16),
height: normalize(16)
},
options: {
flexDirection: "row",
alignItems: "center",
paddingTop: normalize(15),
justifyContent: "center",
paddingHorizontal: normalize(20)
},
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.1)',
},
});
return ( return (
<View style={styles.body}> <View>
<View style={styles.backgroundSection}> <View style={styles.backgroundSection}>
<Image <Image
blurRadius={133}
style={styles.back_drop} style={styles.back_drop}
source={{ source={{
uri: currentspot.cover, uri: item.cover,
}} }}
></Image> />
<LinearGradient style={styles.gradientFade} <View style={styles.overlay} />
colors={['rgba(56,56,56,0)', 'rgba(14,14,14,1)']}> <BlurView
</LinearGradient> style={styles.gradientFade}
intensity={70}
>
<LinearGradient
colors={['rgba(56,0,56,0)', 'rgba(14,14,14,1)']}
style={styles.gradientFade}
/>
</BlurView>
</View> </View>
<View style={styles.background1}> <SafeAreaView style={styles.mainSafeArea}>
<ScrollView style={styles.list} showsVerticalScrollIndicator={false} scrollEventThrottle={4}> <ScrollView>
<View style={styles.section1}>
<View style={{ flex: 1, justifyContent: 'flex-start', alignItems: 'center' }}>
<View>
<Animated.Image <View style={styles.card}>
source={{ <TouchableOpacity onPress={() => { Linking.openURL(item.url); }}>
uri: currentspot.cover, <Animated.Image source={{ uri: item.cover }} style={[styles.cardCover, styleAnimatedImage]} />
}} </TouchableOpacity>
style={[
{
width: normalize(429),
height: normalize(429),
borderRadius: 24,
resizeMode: 'stretch',
}, styleAniamatedImage
]}
/>
</View> </View>
<View style={{ marginTop: 45, flex: 1, flexDirection: 'row', }}> <View style={styles.section1}>
<View> <View style={styles.section2}>
<TouchableOpacity style={{ flexDirection: "row", alignItems: "center" }} onPress={() => { Linking.openURL(item.url); }}>
<Text numberOfLines={1} style={styles.title}>{item.name}</Text>
{item.explicit && (
<Image style={styles.explicitImage} source={require('../assets/images/explicit_icon.png')} />
)}
</TouchableOpacity>
<View style={styles.section3}>
<TouchableOpacity style={styles.buttonArtist} onPress={() => { Linking.openURL(item.artists[0].url); }}>
{artistImage && (
<Image style={{ width: normalize(30), height: normalize(30), borderRadius: 30 }} source={{ uri: artistImage }} />
)}
<Text numberOfLines={1} style={styles.artist}>{item.artists.map((artist: Artist) => artist.name).join(', ')}</Text>
<Text style={styles.date}> - {item.date} - {Math.floor(item.duration / 60)} min {Math.floor(item.duration % 60)} s</Text>
</TouchableOpacity>
</View>
</View> </View>
<TouchableOpacity activeOpacity={0.5} onPressIn={handlePlaySound}
onPressOut={handleStopSound} style={{ {item.trackPreviewUrl && (
backgroundColor: '#F80404', <TouchableOpacity style={styles.playButton} onPress={play}>
borderRadius: 100, <View style={styles.bodyPlayButton}>
padding: normalize(23) <Image style={styles.imagePlayButton} source={isPlaying ? require('../assets/images/play_icon.png') : require('../assets/images/pause_icon.png')} />
}}>
<View style={{ flex: 1, justifyContent: 'center', alignContent: 'center' }}>
</View> </View>
</TouchableOpacity> </TouchableOpacity>
)}
</View> </View>
<View style={styles.options}>
<TouchableOpacity onPress={addToPlaylist}>
<View style={styles.saveButton}>
<Image style={styles.saveIcon} source={addedToPlaylist ? require('../assets/images/save_icon_full.png') : require('../assets/images/save_icon.png')} />
<Text style={styles.saveText}>Dans ma collection</Text>
</View> </View>
</TouchableOpacity>
<TouchableOpacity onPress={onShare}>
<View style={styles.shareButton}>
<Image style={styles.shareIcon} source={require('../assets/images/share_icon.png')} />
<Text style={styles.shareText}>Partager cette music</Text>
</View>
</TouchableOpacity>
</View> </View>
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-evenly', width: '100%' }}>
<TouchableOpacity onPress={addToPlaylist} activeOpacity={0.6} style={{
flexDirection: 'row', justifyContent: 'space-evenly', alignItems: 'center', width: 180,
height: 64, borderRadius: 8, opacity: 0.86, backgroundColor: '#0B0606',
}}>
<Text style={{ fontSize: normalize(16), fontWeight: "700", color: '#FFFFFF' }}>Dans ma collection</Text>
</TouchableOpacity>
<TouchableOpacity onPress={onShare} activeOpacity={0.6} style={{
flexDirection: 'row', justifyContent: 'space-evenly', alignItems: 'center', width: 180,
height: 64, borderRadius: 8, opacity: 0.86, backgroundColor: '#0B0606',
}}>
<Icon name="share" size={24} color="#FFFF"></Icon>
{/* <FontAwesome name="bookmark" size={24} color="#FF0000" ></FontAwesome> */}
<Text style={{ fontSize: normalize(16), fontWeight: "700", color: '#FFFFFF' }}>Partager cette music</Text>
</TouchableOpacity>
</View> <View style={{ paddingTop: normalize(25) }}>
{simularMusic.length !== 0 && ( <Text style={styles.similarTitle} >Similaire</Text>
<HorizontalFlatList title={'Similar'} data={simularMusic}> {loading ? (
{(props) => ( <ActivityIndicator size="large" style={{ paddingTop: normalize(14) }} color="#FFF" />
<Pressable ) :
<FlatList
showsHorizontalScrollIndicator={false}
data={simularMusic}
horizontal={true}
keyExtractor={item => item.id}
renderItem={({ item }) =>
<TouchableOpacity
onPress={() => { onPress={() => {
// @ts-ignore // @ts-ignore
navigator.replace("Detail", { "music": props }) }} > navigator.replace("Detail", { "music": item })
<LittleCard data={props} /> }} >
</Pressable> <SimilarMusic music={item} />
)} </TouchableOpacity>
</HorizontalFlatList> }
)} />}
</ScrollView>
</View> </View>
</ScrollView>
</SafeAreaView>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
mainSafeArea: {
flex: 1,
backgroundColor: "#141414",
},
body: {
backgroundColor: "#0E0E0E"
},
backgroundSection: {
height: "100%",
width: "100%",
position: "absolute"
},
back_drop: {
height: "160%",
width: '430%',
position: "absolute",
},
gradientFade: {
height: "100%",
},
background1: {
height: '100%',
width: '100%',
},
list: {
height: "100%"
},
section1: {
paddingHorizontal: 25
}
})

@ -1,18 +1,16 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { StyleSheet, Text, View, FlatList, SafeAreaView } from 'react-native'; import { StyleSheet, Text, View, FlatList, SafeAreaView, SectionList, TouchableOpacity } from 'react-native';
import CardMusic from '../components/CardMusicComponent'; import CardMusic from '../components/CardMusicComponent';
import normalize from '../components/Normalize'; import normalize from '../components/Normalize';
import { Svg, Path } from 'react-native-svg'; import { Svg, Path } from 'react-native-svg';
import FladyComponent from '../components/FladyComponent'; import FladyComponent from '../components/FladyComponent';
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { useSelector } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { colorsDark } from '../constants/colorsDark'; import { colorsDark } from '../constants/colorsDark';
import { colorsLight } from '../constants/colorsLight'; import { colorsLight } from '../constants/colorsLight';
import { useDispatch } from 'react-redux';
import { getFavoriteMusic } from '../redux/thunk/appThunk'; import { getFavoriteMusic } from '../redux/thunk/appThunk';
import { Spot } from '../models/Spot'; import { Spot } from '../model/Spot';
import { TouchableOpacity } from 'react-native-gesture-handler'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Artist from '../models/Artist';
export default function FavoriteScreen() { export default function FavoriteScreen() {
@ -34,14 +32,46 @@ export default function FavoriteScreen() {
useEffect(() => { useEffect(() => {
//@ts-ignore //@ts-ignore
dispatch(getFavoriteMusic()) dispatch(getFavoriteMusic());
}, []); }, []);
const groupByDate = (data: Spot[]) => {
const groupedData: { [key: string]: Spot[] } = {};
const sortedData = data.sort((a, b) => b.date.getTime() - a.date.getTime());
sortedData.forEach((item) => {
const formattedDate = formatDate(item.date);
if (groupedData[formattedDate]) {
groupedData[formattedDate].push(item);
} else {
groupedData[formattedDate] = [item];
}
});
return Object.keys(groupedData).map((date) => ({
title: date,
data: groupedData[date],
}));
};
const formatDate = (date: Date): string => {
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
return `${day}/${month}/${year}`;
};
const insets = useSafeAreaInsets();
const styles = StyleSheet.create({ const styles = StyleSheet.create({
mainSafeArea: { mainSafeArea: {
flex: 1, flex: 1,
backgroundColor: style.body, backgroundColor: style.body,
paddingTop: insets.top
}, },
titleContainer: { titleContainer: {
marginVertical: 10, marginVertical: 10,
@ -63,6 +93,13 @@ export default function FavoriteScreen() {
fontSize: normalize(20), fontSize: normalize(20),
color: '#787878', color: '#787878',
marginBottom: 5 marginBottom: 5
},
titleSection: {
fontSize: normalize(20),
color: style.Text,
fontWeight: 'medium',
marginLeft: 20,
marginBottom: 10
} }
}); });
@ -78,15 +115,22 @@ export default function FavoriteScreen() {
</View> </View>
<Text style={styles.description}>Retrouvez ici vos musiques favorites</Text> <Text style={styles.description}>Retrouvez ici vos musiques favorites</Text>
</View> </View>
<FlatList <SectionList
data={favoriteMusic} sections={groupByDate(favoriteMusic)}
keyExtractor={(item: Spot) => item.music.id} keyExtractor={(item: Spot) => item.music.id}
renderItem={({ item }) => ( renderItem={({ item }) => (
<TouchableOpacity
onPress={() => {
//@ts-ignore //@ts-ignore
<TouchableOpacity onPress={() => { navigation.navigate("Detail", { "music": item.music }) }}> navigation.navigate('Detail', { music: item.music });
<CardMusic image={item.music.cover} title={item.music.name} description={item.music.artists.map((artist: Artist) => artist.name).join(', ')} id={item.music.id} /> }}>
<CardMusic music={item.music} />
</TouchableOpacity> </TouchableOpacity>
)} )}
renderSectionHeader={({ section: { title } }) => (
//@ts-ignore
<Text style={styles.titleSection}>{title}</Text>
)}
ListFooterComponent={ ListFooterComponent={
<> <>
<Text style={[styles.title, { marginLeft: 20 }]}>What's your mood?</Text> <Text style={[styles.title, { marginLeft: 20 }]}>What's your mood?</Text>
@ -96,13 +140,12 @@ export default function FavoriteScreen() {
keyExtractor={(item) => item.id.toString()} keyExtractor={(item) => item.id.toString()}
horizontal horizontal
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
renderItem={({ item }) => ( renderItem={({ item }) => <FladyComponent image={item.source} />}
<FladyComponent image={item.source} />
)}
/> />
</> </>
} }
nestedScrollEnabled={true} nestedScrollEnabled={true}
/> />
</SafeAreaView> </SafeAreaView>
); );

@ -11,8 +11,9 @@ import { SafeAreaView } 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 { setMail, setName } from '../redux/thunk/userThunk'; import { setImage, setMail, setName, setPassword } from '../redux/thunk/userThunk';
import { setErrorUpdate } from '../redux/actions/userActions'; import { setErrorUpdate } from '../redux/actions/userActions';
import * as FileSystem from 'expo-file-system';
// @ts-ignore // @ts-ignore
const DismissKeyboard = ({ children }) => ( const DismissKeyboard = ({ children }) => (
@ -32,6 +33,9 @@ export default function ProfilScreen() {
const userCurrent = useSelector(state => state.userReducer.user); const userCurrent = useSelector(state => state.userReducer.user);
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [oldPassword, setOldPassword] = React.useState('');
const [newPassword, setNewPassword] = React.useState('');
const [confirmPassword, setConfirmPassword] = React.useState('');
const style = isDark ? colorsDark : colorsLight; const style = isDark ? colorsDark : colorsLight;
const navigation = useNavigation(); const navigation = useNavigation();
const [isModalVisible, setIsModalVisible] = React.useState(false); const [isModalVisible, setIsModalVisible] = React.useState(false);
@ -60,12 +64,25 @@ export default function ProfilScreen() {
}; };
const pickImage = async () => { const pickImage = async () => {
await ImagePicker.launchImageLibraryAsync({ const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All, mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true, allowsEditing: true,
aspect: [4, 3], aspect: [3, 3],
quality: 1, quality: 0.2,
}); });
if (result.assets !== null) {
const base64Image = await convertImageToBase64(result.assets[0].uri);
//@ts-ignore
dispatch(setImage(base64Image));
}
};
const convertImageToBase64 = async (imageUri: any) => {
const base64 = await FileSystem.readAsStringAsync(imageUri, {
encoding: FileSystem.EncodingType.Base64,
});
return `data:image/jpg;base64,${base64}`;
}; };
const submitUsername = () => { const submitUsername = () => {
@ -134,6 +151,14 @@ export default function ProfilScreen() {
); );
} }
const submitPassword = () => {
//@ts-ignore
dispatch(setPassword(oldPassword, newPassword));
setOldPassword("");
setNewPassword("");
setConfirmPassword("");
}
useEffect(() => { useEffect(() => {
if (errorUpdate) { if (errorUpdate) {
Alert.alert( Alert.alert(
@ -355,6 +380,7 @@ export default function ProfilScreen() {
textInputConfirmModal: { textInputConfirmModal: {
marginLeft: 30, marginLeft: 30,
color: style.Text, color: style.Text,
width: '67.5%',
fontSize: normalize(18) fontSize: normalize(18)
}, },
textInputOldModal: { textInputOldModal: {
@ -445,28 +471,35 @@ export default function ProfilScreen() {
</View> </View>
</TouchableOpacity> </TouchableOpacity>
<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}
onPress={() => submitPassword()}>
<View> <View>
<Text style={styles.updateText}>Modifier</Text> <Text style={[styles.updateText, {
color: newPassword.length >= 6 && newPassword === confirmPassword && oldPassword.length >= 6 ? '#1c77fb' : '#404040',
}]}>Modifier</Text>
</View> </View>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={styles.bodyModal}> <View style={styles.bodyModal}>
<View style={styles.optionModalWithUnderline}> <View style={styles.optionModalWithUnderline}>
<Text style={styles.textOptionModal}>Ancien</Text> <Text style={styles.textOptionModal}>Ancien</Text>
<TextInput placeholderTextColor='#828288' placeholder="saisir l'ancien mot de passe" style={styles.textInputOldModal} /> <TextInput placeholderTextColor='#828288' value={oldPassword} secureTextEntry={true}
onChangeText={setOldPassword} placeholder="saisir l'ancien mot de passe" style={styles.textInputOldModal} />
</View> </View>
<View style={styles.optionModalWithUnderline}> <View style={styles.optionModalWithUnderline}>
<Text style={styles.textOptionModal}>Nouveau</Text> <Text style={styles.textOptionModal}>Nouveau</Text>
<TextInput placeholderTextColor='#828288' placeholder='saisir le mot de passe' style={styles.textInputNewModal} /> <TextInput placeholderTextColor='#828288' value={newPassword} secureTextEntry={true}
onChangeText={setNewPassword} placeholder='saisir le mot de passe' style={styles.textInputNewModal} />
</View> </View>
<View style={styles.optionModal}> <View style={styles.optionModal}>
<Text style={styles.textOptionModal}>Confirmer</Text> <Text style={styles.textOptionModal}>Confirmer</Text>
<TextInput placeholderTextColor='#828288' placeholder='mot de passe' style={styles.textInputConfirmModal} /> <TextInput placeholderTextColor='#828288' value={confirmPassword} secureTextEntry={true}
onChangeText={setConfirmPassword} placeholder='mot de passe' style={styles.textInputConfirmModal} />
</View> </View>
</View> </View>
<View style={styles.warningView}> <View style={styles.warningView}>
<Text style={styles.warning}>Votre mot de passe doit comporter au moins 8 caractères, dont au moins un chiffre, une majuscule et une minuscule.</Text> <Text style={styles.warning}>Votre mot de passe doit comporter au moins 6 caractères.</Text>
</View> </View>
</View> </View>
</Modal> </Modal>

@ -11,8 +11,8 @@ import { logout } from '../redux/thunk/authThunk';
import { darkMode } from '../redux/thunk/userThunk'; import { darkMode } from '../redux/thunk/userThunk';
import { colorsDark } from '../constants/colorsDark'; import { colorsDark } from '../constants/colorsDark';
import { colorsLight } from '../constants/colorsLight'; import { colorsLight } from '../constants/colorsLight';
import { User } from '../models/User'; import { User } from '../model/User';
import Artist from '../models/Artist'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
// @ts-ignore // @ts-ignore
const DismissKeyboard = ({ children }) => ( const DismissKeyboard = ({ children }) => (
@ -63,10 +63,15 @@ export default function SettingScreen() {
const toggleLocalisation = const toggleLocalisation =
() => setIsCheckedLocalisation(value => !value); () => setIsCheckedLocalisation(value => !value);
const insets = useSafeAreaInsets();
const styles = StyleSheet.create({ const styles = StyleSheet.create({
mainSafeArea: { mainSafeArea: {
flex: 1, flex: 1,
backgroundColor: style.body, backgroundColor: style.body,
paddingTop: insets.top
}, },
container: { container: {
marginTop: 30, marginTop: 30,
@ -92,7 +97,7 @@ export default function SettingScreen() {
inputSearch: { inputSearch: {
placeholderTextColor: 'red', placeholderTextColor: 'red',
color: style.Text, color: style.Text,
width: normalize(350), width: "80%",
}, },
profil: { profil: {
paddingVertical: 9, paddingVertical: 9,
@ -336,7 +341,7 @@ export default function SettingScreen() {
</View> </View>
<View style={styles.musicActually}> <View style={styles.musicActually}>
<CardMusic image={currentMusic.cover} title={currentMusic.name} description={currentMusic.artists.map((artist: Artist) => artist.name).join(', ')} id='1' /> <CardMusic music={currentMusic} />
<Image source={require("../assets/images/flady_icon.png")} style={styles.mascot} /> <Image source={require("../assets/images/flady_icon.png")} style={styles.mascot} />
</View> </View>
</> </>
@ -353,7 +358,7 @@ export default function SettingScreen() {
<Text style={styles.textDeconnectionOption}>Se deconnecter</Text> <Text style={styles.textDeconnectionOption}>Se deconnecter</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={{alignItems: 'center'}}> <View style={{ alignItems: 'center' }}>
<Text style={styles.creationDateText}>Compte créer le {currentUser.creationDate.toLocaleString('fr-FR', { <Text style={styles.creationDateText}>Compte créer le {currentUser.creationDate.toLocaleString('fr-FR', {
year: 'numeric', year: 'numeric',
month: 'long', month: 'long',

@ -9,9 +9,8 @@ import LottieView from 'lottie-react-native'
import Lotties from '../assets/lottie/Lottie'; import Lotties from '../assets/lottie/Lottie';
import Loading from '../components/LoadingComponent'; import Loading from '../components/LoadingComponent';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { addFavoritesMusic } from '../redux/actions/appActions';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Spot } from '../models/Spot'; import { Spot } from '../model/Spot';
import { removeFromSpotList, setSpotList } from '../redux/actions/spotActions'; import { removeFromSpotList, setSpotList } from '../redux/actions/spotActions';
export default function SpotScreen() { export default function SpotScreen() {
@ -50,7 +49,7 @@ export default function SpotScreen() {
function addLike(spot: Spot) { function addLike(spot: Spot) {
onLike(); onLike();
dispatch(addFavoritesMusic(spot.music)) //dispatch(addFavoritesMusic(spot.music))
dispatch(removeFromSpotList(spot)); dispatch(removeFromSpotList(spot));
} }
function removeSpots(spot: Spot) { function removeSpots(spot: Spot) {

@ -1,4 +1,4 @@
import Music from "../../../models/Music"; import Music from "../../../model/Music";
export default interface IMusicService { export default interface IMusicService {
getMusicById(id: string): Promise<Music>; getMusicById(id: string): Promise<Music>;
@ -8,4 +8,5 @@ export default interface IMusicService {
getMusicsWithName(name: string): Promise<Music[]>; getMusicsWithName(name: string): Promise<Music[]>;
addToPlaylist(idTrack: string): void; addToPlaylist(idTrack: string): void;
getSimilarTracks(idTrack: string): Promise<Music[]>; getSimilarTracks(idTrack: string): Promise<Music[]>;
getImageArtistWithId(idArtist: string): Promise<string | null>;
} }

@ -1,8 +1,8 @@
import axios from "axios"; import axios from "axios";
import Music from "../../../models/Music"; import Music from "../../../model/Music";
import IMusicService from "../interfaces/IMusicService"; import IMusicService from "../interfaces/IMusicService";
import TokenSpotify from "./TokenSpotify"; import TokenSpotify from "./TokenSpotify";
import MusicMapper from "../../../models/mapper/MusicMapper"; import MusicMapper from "../../../model/mapper/MusicMapper";
export default class SpotifyService implements IMusicService { export default class SpotifyService implements IMusicService {
private readonly API_URL = "https://api.spotify.com/v1"; private readonly API_URL = "https://api.spotify.com/v1";
@ -186,4 +186,20 @@ export default class SpotifyService implements IMusicService {
} }
} }
async getImageArtistWithId(idArtist: string): Promise<string | null> {
const access_token = await this._token.getAccessToken();
try {
const response = await axios.get(`${this.API_URL}/artists/${idArtist}`, {
headers: {
'Authorization': `Bearer ${access_token}`
},
});
return response.data.images[0].url;
} catch (error: any) {
console.log(error)
return null;
}
}
} }
Loading…
Cancel
Save