@ -1,9 +1,17 @@
|
|||||||
|
import Music from "./Music";
|
||||||
|
|
||||||
class Spot {
|
export class Spot {
|
||||||
private userId : string;
|
private _userId : string;
|
||||||
public music : Music;
|
public _music : Music;
|
||||||
constructor(userId : string, music : Music){
|
constructor(userId : string, music : Music){
|
||||||
this.userId = userId;
|
this._userId = userId;
|
||||||
this.music = music;
|
this._music = music;
|
||||||
}
|
}
|
||||||
|
get userSpotifyId(): string {
|
||||||
|
return this._userId;
|
||||||
|
}
|
||||||
|
get idSpotify(): Music {
|
||||||
|
return this._music;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
class TokenSpotify {
|
||||||
|
String _accessToken;
|
||||||
|
final String _refreshToken;
|
||||||
|
late DateTime _tokenEnd;
|
||||||
|
|
||||||
|
// TokenSpotify(this._accessToken, this._refreshToken, int expiresIn) {
|
||||||
|
// _setTokenEnd(expiresIn);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _setTokenEnd(int expiresIn) {
|
||||||
|
// _tokenEnd = DateTime.now().add(Duration(seconds: expiresIn));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Future<String> getAccessToken() async {
|
||||||
|
// if (DateTime.now().isAfter(_tokenEnd)) {
|
||||||
|
// await _actualiseToken();
|
||||||
|
// }
|
||||||
|
// return _accessToken;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _actualiseToken() async {
|
||||||
|
// var urlToken = Uri.https('accounts.spotify.com', 'api/token', {
|
||||||
|
// 'grant_type': 'refresh_token',
|
||||||
|
// 'refresh_token': _refreshToken,
|
||||||
|
// 'client_id': ApiSpotifyIdentification.clientId
|
||||||
|
// });
|
||||||
|
// setResponse(await http.post(urlToken, headers: <String, String>{
|
||||||
|
// 'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
// }));
|
||||||
|
// var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map;
|
||||||
|
// _accessToken = decodedResponse['access_token'];
|
||||||
|
// _setTokenEnd(decodedResponse['expires_in']);
|
||||||
|
// }
|
||||||
|
}
|
@ -1,9 +1,42 @@
|
|||||||
class User {
|
export class User {
|
||||||
//attributes from DAFL
|
//attributes from DAFL
|
||||||
private idFlad : any;;
|
private _idFlad : string;
|
||||||
private idSpotify : any;
|
private _idSpotify : string;
|
||||||
|
private _email : string;
|
||||||
|
private _createdAt : Date;
|
||||||
|
private _name : string;
|
||||||
|
public image : string = require('../assets/images/jul.png');
|
||||||
//constructors
|
//constructors
|
||||||
constructor(){
|
constructor(idFlad : string, idSpotify : string, email : string, createdAt : Date, name : string, image : string){
|
||||||
|
this._name = name;
|
||||||
|
this._idFlad = idFlad;
|
||||||
|
this._idSpotify = idSpotify;
|
||||||
|
this._createdAt = createdAt;
|
||||||
|
this._email = email;
|
||||||
|
this.image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
get idFlad(): string {
|
||||||
|
return this._idFlad;
|
||||||
|
}
|
||||||
|
get idSpotify(): string {
|
||||||
|
return this._idSpotify;
|
||||||
|
}
|
||||||
|
get email(): string {
|
||||||
|
return this._email;
|
||||||
|
}
|
||||||
|
get createAt(): Date {
|
||||||
|
return this._createdAt;
|
||||||
|
}
|
||||||
|
get name(): string {
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static empty() {
|
||||||
|
return new User('','','',new Date(),'',require('../assets/images/jul.png'));
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return 'User : ' + this.idFlad + ', ' + this.name + ', ' + this.idSpotify;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import Music from "../Music";
|
||||||
|
|
||||||
|
export default class MusicFactory {
|
||||||
|
static mapFromSpotifyTrack(jsonMusic :any ): Music {
|
||||||
|
const music = new Music(
|
||||||
|
jsonMusic.id,
|
||||||
|
jsonMusic.name,
|
||||||
|
jsonMusic.album.images[0].url
|
||||||
|
);
|
||||||
|
return music;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import { User } from "../User";
|
||||||
|
|
||||||
|
export class UserFactory {
|
||||||
|
|
||||||
|
public static JsonToModel( jsonUser :any ) : User{
|
||||||
|
return new User(jsonUser.idFlad, jsonUser.idSpotify, jsonUser.email, jsonUser.createdAt, jsonUser.name, jsonUser.imageUrl);
|
||||||
|
}
|
||||||
|
public static uptade( jsonUser :any ) : User{
|
||||||
|
return new User(jsonUser.idFlad, jsonUser.idSpotify, jsonUser.email, jsonUser.createdAt, jsonUser.name, jsonUser.imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
After Width: | Height: | Size: 1.9 MiB |
@ -0,0 +1,6 @@
|
|||||||
|
const Lotties = {
|
||||||
|
likeAnimation: require('./spotify-like-interaction.json')
|
||||||
|
// riveLike : require('./light_like.riv'),
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Lotties;
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,53 @@
|
|||||||
|
import { SharedElement } from "react-navigation-shared-element";
|
||||||
|
import { NavigationProp, RouteProp } from "@react-navigation/native";
|
||||||
|
import { View,Image,StyleSheet, Dimensions, useWindowDimensions } from "react-native";
|
||||||
|
import Animated, { interpolate, SensorType, useAnimatedSensor, useAnimatedStyle, withTiming } from "react-native-reanimated";
|
||||||
|
|
||||||
|
interface SpotProps {
|
||||||
|
spot: { name: string, sourceUrl: string, index : number };
|
||||||
|
}
|
||||||
|
const halfPi = Math.PI/2;
|
||||||
|
|
||||||
|
const AnimatedParalax = ({}) => {
|
||||||
|
const {width, height} = useWindowDimensions();
|
||||||
|
const sensor = useAnimatedSensor(SensorType.ROTATION);
|
||||||
|
const styleAniamatedImage = useAnimatedStyle(() => {
|
||||||
|
const {yaw, pitch, roll} = sensor.sensor.value;
|
||||||
|
const verticalAxis =interpolate(
|
||||||
|
pitch,
|
||||||
|
[-halfPi,halfPi],
|
||||||
|
[-25, 25]
|
||||||
|
)
|
||||||
|
const horizontalAxis =interpolate(
|
||||||
|
roll,
|
||||||
|
[-halfPi*2,halfPi*2],
|
||||||
|
[-35, 35]
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
top : verticalAxis,
|
||||||
|
left : horizontalAxis,
|
||||||
|
};
|
||||||
|
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1, justifyContent : 'flex-start', alignItems : 'center' }}>
|
||||||
|
<Animated.Image
|
||||||
|
source={{
|
||||||
|
uri:spot.sourceUrl ,
|
||||||
|
}}
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
width: 370,
|
||||||
|
height: 370,
|
||||||
|
borderRadius : 24,
|
||||||
|
resizeMode: 'stretch',
|
||||||
|
},styleAniamatedImage
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default AnimatedParalax;
|
@ -0,0 +1,82 @@
|
|||||||
|
import { View, StyleSheet, Dimensions, Image, Pressable, TouchableWithoutFeedback, TouchableOpacity, TouchableHighlight } from "react-native";
|
||||||
|
import Animated, {
|
||||||
|
Layout,
|
||||||
|
Transition,
|
||||||
|
ZoomIn,
|
||||||
|
ZoomOut,
|
||||||
|
} from "react-native-reanimated";
|
||||||
|
|
||||||
|
const { width } = Dimensions.get("window");
|
||||||
|
const SIZE = width / 3;
|
||||||
|
import { Feather as Icon } from "@expo/vector-icons";
|
||||||
|
import Music from "../Model/Music";
|
||||||
|
import { State, TapGestureHandler } from "react-native-gesture-handler";
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
interface ArtistProps {
|
||||||
|
artist: Music;
|
||||||
|
onPress: () => void;
|
||||||
|
}
|
||||||
|
export const Artist = ({ artist, onPress }: ArtistProps) => {
|
||||||
|
const source = typeof artist.image === 'string' ? { uri: artist.image } : artist.image;
|
||||||
|
//@ts-ignore
|
||||||
|
const onSingleTapEvent = (event) => {
|
||||||
|
if (event.nativeEvent.state === State.ACTIVE) {
|
||||||
|
alert('Hey single tap!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const doubleTapRef = useRef(null);
|
||||||
|
const [selected,setSeleted] = useState(false);
|
||||||
|
const onS = () => {
|
||||||
|
setSeleted(!selected);
|
||||||
|
onPress();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress={onS}>
|
||||||
|
<Animated.View
|
||||||
|
style={styles.container}
|
||||||
|
entering={ZoomIn}
|
||||||
|
exiting={ZoomOut}
|
||||||
|
layout={Layout.delay(200)}
|
||||||
|
>
|
||||||
|
<View style={styles.card}>
|
||||||
|
<Image source={source} style={styles.image} />
|
||||||
|
{ selected && (
|
||||||
|
<View style={styles.cheked}>
|
||||||
|
<Icon name="check-circle" color="black" size={24} />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
width: SIZE,
|
||||||
|
height: SIZE,
|
||||||
|
padding: 8,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 8,
|
||||||
|
alignItems: "flex-end",
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
borderRadius: 8,
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
width: undefined,
|
||||||
|
height: undefined,
|
||||||
|
},
|
||||||
|
cheked : {
|
||||||
|
backgroundColor : "white",
|
||||||
|
borderRadius : 100,
|
||||||
|
alignItems : "center",
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,76 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { View, Text, Image ,PanResponder, Dimensions, StyleSheet, ImageBackground, Button, Pressable, TextInput } from 'react-native'
|
||||||
|
import Animated, { interpolate, lessThan, multiply, useAnimatedStyle } from 'react-native-reanimated';
|
||||||
|
import HalfCirlce from './HalfCircle';
|
||||||
|
|
||||||
|
interface CircularProps {
|
||||||
|
background : string,
|
||||||
|
foreground : string,
|
||||||
|
progress : Animated.Value<number>,
|
||||||
|
radius : number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PI= Math.PI;
|
||||||
|
const FladInput = ({background, foreground, progress} : CircularProps) => {
|
||||||
|
const [focused, setFocused] = useState<boolean>(false);
|
||||||
|
const theta = multiply(progress,2*PI);
|
||||||
|
const rotateTop = theta;
|
||||||
|
const opacity = lessThan(theta, PI);
|
||||||
|
|
||||||
|
|
||||||
|
const rotateAnimation = useAnimatedStyle(() => {
|
||||||
|
const rotate = interpolate
|
||||||
|
( theta,
|
||||||
|
[PI, 2*PI],
|
||||||
|
[0,PI]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
transform: [
|
||||||
|
{rotate: rotate},
|
||||||
|
{translateX: RADUIS/2},
|
||||||
|
{translateY: RADUIS/2}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const rotateAnimation2 = useAnimatedStyle(() => {
|
||||||
|
const rotate = interpolate
|
||||||
|
( theta,
|
||||||
|
[PI, 2*PI],
|
||||||
|
[0,PI]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
transform: [
|
||||||
|
{rotate: theta},
|
||||||
|
{translateX: RADUIS/2},
|
||||||
|
{translateY: RADUIS/2}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<View style={{zIndex : 1}}>
|
||||||
|
<HalfCirlce backgroundColor={background}/>
|
||||||
|
<Animated.View style={{...StyleSheet.absoluteFillObject, transform : [{rotate : '180%'}], opacity}}>
|
||||||
|
<HalfCirlce backgroundColor={background}/>
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
<View style={{ transform : [{rotate : '180%'}]}}>
|
||||||
|
<HalfCirlce backgroundColor={background}/>
|
||||||
|
<Animated.View style={{}}>
|
||||||
|
<HalfCirlce backgroundColor={background}/>
|
||||||
|
</Animated.View> </View>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
input : {
|
||||||
|
justifyContent : 'center',
|
||||||
|
alignItems : 'center',
|
||||||
|
placeholder : "placeholde"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default FladInput;
|
@ -0,0 +1,114 @@
|
|||||||
|
import { View, Text, Image , Dimensions, StyleSheet } from 'react-native'
|
||||||
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
|
import Animated,{ Extrapolate, interpolate, runOnJS, useAnimatedGestureHandler, useAnimatedStyle, useSharedValue, withRepeat, withSpring, withTiming } from 'react-native-reanimated';
|
||||||
|
|
||||||
|
import { PanGestureHandler, PanGestureHandlerGestureEvent } from 'react-native-gesture-handler';
|
||||||
|
import * as Haptics from 'expo-haptics';
|
||||||
|
|
||||||
|
const {width : wWidht} = Dimensions.get("window");
|
||||||
|
const SCREEN_HEIGHT = Dimensions.get('window').height
|
||||||
|
const SCREEN_WIDTH = Dimensions.get('window').width
|
||||||
|
// const width = wWidht *0.75;
|
||||||
|
// const height = wWidht * (465/264);
|
||||||
|
// const borderRadius = 24;
|
||||||
|
|
||||||
|
const size= 100
|
||||||
|
const FladLoading = () => {
|
||||||
|
|
||||||
|
const progresse = useSharedValue(1);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// withTiming, withSpring
|
||||||
|
progresse.value =withRepeat( withTiming(0.01,{duration : 750}), -1,true);
|
||||||
|
}, [progresse]);
|
||||||
|
|
||||||
|
const breatheStyle = useAnimatedStyle(() => {
|
||||||
|
const borderRange = interpolate
|
||||||
|
( progresse.value,
|
||||||
|
[0, 1],
|
||||||
|
[(0*size) / 2,(1*size)/2],
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
justifyContent : 'center',
|
||||||
|
alignItems : 'center',
|
||||||
|
width : size,
|
||||||
|
height : size,
|
||||||
|
shadowColor : "#DA1D1D",
|
||||||
|
shadowOffset : {width : 0, height : 0},
|
||||||
|
shadowOpacity : 1,
|
||||||
|
shadowRadius :borderRange,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const breatheStyle2 = useAnimatedStyle(() => {
|
||||||
|
const borderRange = interpolate
|
||||||
|
( progresse.value,
|
||||||
|
[0, 1],
|
||||||
|
[(0*size) / 2,(1*size)/2],
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
|
||||||
|
borderRadius : borderRange,
|
||||||
|
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const breatheStyleSquare = useAnimatedStyle(() => {
|
||||||
|
const borderRange = interpolate
|
||||||
|
( progresse.value,
|
||||||
|
[0, 1],
|
||||||
|
[(size+20),(size)],
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
|
||||||
|
width : borderRange,
|
||||||
|
height : borderRange,
|
||||||
|
borderRadius : borderRange/2,
|
||||||
|
borderWidth : size/10,
|
||||||
|
borderColor : "#F80404",
|
||||||
|
shadowColor : "#F40C1C",
|
||||||
|
shadowOffset : {width : 0, height : 0},
|
||||||
|
shadowOpacity : 1,
|
||||||
|
shadowRadius :10,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{flex : 1, justifyContent : 'center', alignItems :'center'}}>
|
||||||
|
|
||||||
|
<Animated.View style={[{backgroundColor : '#B40404', justifyContent : 'center', alignItems : 'center'}, breatheStyleSquare]}>
|
||||||
|
<Animated.Image source={require('../assets/icons/icon.png')} style={[{height: size, width : size, borderColor : '#fff',borderRadius : size/2}]}/>
|
||||||
|
</Animated.View>
|
||||||
|
{/* <Animated.View style={[ {backgroundColor : 'green'},breatheStyleSquare]}>
|
||||||
|
</Animated.View> */}
|
||||||
|
|
||||||
|
{/* <Image source={require('../assets/icons/Spotify_-_Animation_1.gif')}/> */}
|
||||||
|
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
card : {
|
||||||
|
justifyContent : 'center',
|
||||||
|
alignItems : 'center',
|
||||||
|
},
|
||||||
|
image : {
|
||||||
|
borderRadius : 24,
|
||||||
|
resizeMode: 'stretch',
|
||||||
|
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default FladLoading;
|
||||||
|
|
@ -0,0 +1,83 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { FlatList, ScrollView } from "react-native";
|
||||||
|
import Music from "../Model/Music";
|
||||||
|
import { Artist } from "./Artist";
|
||||||
|
import { StyleSheet } from "react-native";
|
||||||
|
|
||||||
|
export const ArtistLayout = () => {
|
||||||
|
const MUSIC_LIST : Music[] = [
|
||||||
|
new Music("La pharmacie", "Jul",require("../assets/images/jul.png")),
|
||||||
|
new Music("Deux frères", "PNL", require("../assets/images/pnl.png")),
|
||||||
|
new Music("Bambina", "PNL", "https://upload.wikimedia.org/wikipedia/en/a/a0/PNL_-_Dans_la_l%C3%A9gende.png"),
|
||||||
|
new Music("Stratos", "Kekra", "https://images.genius.com/ddc9cadedd1d4cef0860aaa85af9cd46.705x705x1.png"),
|
||||||
|
new Music("Autobahn", "Sch", "https://images.genius.com/83b6c98680d38bde1571f6b4093244b5.1000x1000x1.jpg"),
|
||||||
|
new Music("Freeze Raël", "Freeze Corleone", "https://intrld.com/wp-content/uploads/2020/08/freeze-corleone-la-menace-fanto%CC%82me.png"),
|
||||||
|
new Music("Blanka", "PNL", require("../assets/images/pnl.png")),
|
||||||
|
new Music("Kratos", "PNL", "https://upload.wikimedia.org/wikipedia/en/a/a0/PNL_-_Dans_la_l%C3%A9gende.png"),
|
||||||
|
]
|
||||||
|
const [artists, setArtists] = useState<Music[]>(MUSIC_LIST);
|
||||||
|
const [selectedArtists, setSelectedArtists] = useState<typeof MUSIC_LIST> ([]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView contentContainerStyle={styles.container}>
|
||||||
|
{artists.map((artist, i) => (
|
||||||
|
<Artist
|
||||||
|
artist={artist}
|
||||||
|
key={artist.title}
|
||||||
|
onPress={() => {
|
||||||
|
// artists.splice(i, 1);
|
||||||
|
// // 2 implementation
|
||||||
|
const tmppArtist = new Music("Kratos", "PNL", "https://upload.wikimedia.org/wikipedia/en/a/a0/PNL_-_Dans_la_l%C3%A9gende.png");
|
||||||
|
|
||||||
|
// const existingObjIndex = selectedArtists.findIndex(obj => obj.title === tmppArtist.title);
|
||||||
|
// if (existingObjIndex === -1) {
|
||||||
|
// selectedArtists.push(tmppArtist);
|
||||||
|
// } else {
|
||||||
|
// selectedArtists.splice(existingObjIndex, 1);
|
||||||
|
// }
|
||||||
|
// setSelectedArtists([...selectedArtists]);
|
||||||
|
// 1 implementation
|
||||||
|
// setSelectedArtists(selectedArtists.findIndex(obj => obj.title === tmppArtist.title) === -1
|
||||||
|
// ? [...selectedArtists, tmppArtist]
|
||||||
|
// : selectedArtists.filter(obj => obj.title !== tmppArtist.title))
|
||||||
|
// 3 implementations
|
||||||
|
// use the selectedProps of the Artist Component
|
||||||
|
// then when we need to finish
|
||||||
|
// onPress{ () => setSelectedArtists([...selectedArtists,artists.filter(artist => artist.selected)])}
|
||||||
|
|
||||||
|
artists.push(tmppArtist);
|
||||||
|
setArtists([...artists]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{/* <FlatList
|
||||||
|
data={artists}
|
||||||
|
// need to reverse colums oreder
|
||||||
|
numColumns = {3}
|
||||||
|
renderItem={({ item }) => (
|
||||||
|
<Artist
|
||||||
|
artist={item}
|
||||||
|
key={item.title}
|
||||||
|
onPress={() => {
|
||||||
|
artists.push(new Music("Kratos", "PNL", "https://upload.wikimedia.org/wikipedia/en/a/a0/PNL_-_Dans_la_l%C3%A9gende.png"));
|
||||||
|
setArtists([...artists]);
|
||||||
|
}}/>
|
||||||
|
)}
|
||||||
|
keyExtractor={(item: Music) => item.title }
|
||||||
|
// ListEmptyComponent = {}
|
||||||
|
/> */}
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flexDirection: "row",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,33 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { View, Text, Image, Animated ,PanResponder, Dimensions, StyleSheet, ImageBackground, Button, Pressable, TextInput } from 'react-native'
|
||||||
|
|
||||||
|
interface HalfCirlceProps {
|
||||||
|
backgroundColor : string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HalfCirlce = ({backgroundColor} : HalfCirlceProps) => {
|
||||||
|
const [focused, setFocused] = useState<boolean>(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{
|
||||||
|
width : RADUIS* 2,
|
||||||
|
height : RADUIS* 2,
|
||||||
|
overflow : "hidden",
|
||||||
|
|
||||||
|
}}>
|
||||||
|
<View style={{backgroundColor : backgroundColor, width : RADUIS* 2, height : RADUIS * 2, borderRadius : RADUIS, }}>
|
||||||
|
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
input : {
|
||||||
|
justifyContent : 'center',
|
||||||
|
alignItems : 'center',
|
||||||
|
placeholder : "placeholde"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default HalfCirlce;
|
@ -1,12 +1,37 @@
|
|||||||
export const cards = [{
|
export const cards = [{
|
||||||
name : "blue",
|
name : "blue",
|
||||||
sourceUrl : "https://i.ebayimg.com/images/g/rY0AAOSw97djEo2C/s-l500.jpg",
|
sourceUrl : "https://th.bing.com/th/id/R.dbf87f0d8cbfd078ab6a589a5d921994?rik=1%2f6KliMpOAeh8A&pid=ImgRaw&r=0",
|
||||||
index : 3
|
index : 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : "her",
|
name : "her",
|
||||||
sourceUrl : "https://i.ebayimg.com/images/g/rY0AAOSw97djEo2C/s-l500.jpg",
|
sourceUrl : "https://i.ebayimg.com/images/g/rY0AAOSw97djEo2C/s-l500.jpg",
|
||||||
|
index : 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : "gambino",
|
||||||
|
sourceUrl : "https://th.bing.com/th/id/R.0b2d1a59bfda9b1a49ecb561e08535a8?rik=Xyc35OZU%2f6VOVw&pid=ImgRaw&r=0",
|
||||||
index : 3
|
index : 3
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
name : "PNL",
|
||||||
|
sourceUrl : "https://upload.wikimedia.org/wikipedia/en/a/a0/PNL_-_Dans_la_l%C3%A9gende.png",
|
||||||
|
index : 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : "Freeze Raël",
|
||||||
|
sourceUrl : "https://intrld.com/wp-content/uploads/2020/08/freeze-corleone-la-menace-fanto%CC%82me.png",
|
||||||
|
index : 23
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : "Sch",
|
||||||
|
sourceUrl : "https://images.genius.com/83b6c98680d38bde1571f6b4093244b5.1000x1000x1.jpg",
|
||||||
|
index : 44
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : "Stratos",
|
||||||
|
sourceUrl : "https://images.genius.com/ddc9cadedd1d4cef0860aaa85af9cd46.705x705x1.png",
|
||||||
|
index : 89
|
||||||
|
},
|
||||||
|
|
||||||
]
|
]
|
@ -0,0 +1 @@
|
|||||||
|
export const API_URL = "https://flad-api-production.up.railway.app"
|
@ -0,0 +1,67 @@
|
|||||||
|
import Navigation from './Navigation';
|
||||||
|
import { StyleSheet,SafeAreaView } from 'react-native';
|
||||||
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
|
import StartNavigation from './StartNavigation';
|
||||||
|
import { Provider, useDispatch, useSelector } from 'react-redux';
|
||||||
|
import store from '../redux/store';
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
import * as SplashScreen from 'expo-splash-screen';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import { getRefreshToken } from '../redux/thunk/authThunk';
|
||||||
|
|
||||||
|
SplashScreen.preventAutoHideAsync();
|
||||||
|
|
||||||
|
export default function AuthNavigation() {
|
||||||
|
//@ts-ignore
|
||||||
|
const appIsReady : boolean = useSelector(state => state.userReducer.loading);
|
||||||
|
//@ts-ignore
|
||||||
|
const isLogin : boolean = useSelector(state => state.userReducer.isLogedIn);
|
||||||
|
// const userToken : string = useSelector(state => state.userReducer.userFladToken);
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function prepare() {
|
||||||
|
console.log(appIsReady, "1 AuthNav")
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
await dispatch(getRefreshToken())
|
||||||
|
await SplashScreen.hideAsync();
|
||||||
|
}
|
||||||
|
prepare();
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const onStackRootView = useCallback(async () => {
|
||||||
|
if (appIsReady) {
|
||||||
|
await SplashScreen.hideAsync();
|
||||||
|
}
|
||||||
|
}, [appIsReady]);
|
||||||
|
|
||||||
|
if (appIsReady == false) {
|
||||||
|
console.log(appIsReady, "T9 AuthNav")
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
console.log(appIsReady, "k9 AuthNav")
|
||||||
|
// console.log(userToken, "k9 AuthNav")
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isLogin ? (
|
||||||
|
/* {userToken != null ? ( */
|
||||||
|
<SafeAreaView style={styles.mainSafeArea} >
|
||||||
|
<Navigation/>
|
||||||
|
</SafeAreaView>
|
||||||
|
|
||||||
|
) :
|
||||||
|
<SafeAreaProvider >
|
||||||
|
<StartNavigation/>
|
||||||
|
</SafeAreaProvider>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
mainSafeArea: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: "#141414",
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,38 @@
|
|||||||
|
import React, {Component} from 'react';
|
||||||
|
import FavoritePage from '../screens/favoritePage';
|
||||||
|
import { createStackNavigator } from '@react-navigation/stack';
|
||||||
|
import Spot from '../screens/spot'
|
||||||
|
import { createSharedElementStackNavigator } from "react-navigation-shared-element";
|
||||||
|
import SpotDetailsPage from '../screens/SpotDetailsPage';
|
||||||
|
|
||||||
|
|
||||||
|
export default function SpotNavigation() {
|
||||||
|
// const Stack = createSharedElementStackNavigator();
|
||||||
|
const Stack = createStackNavigator();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack.Navigator screenOptions={{
|
||||||
|
gestureEnabled: false,
|
||||||
|
headerShown: false,
|
||||||
|
cardOverlayEnabled: true,
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack.Screen
|
||||||
|
name="Spots"
|
||||||
|
component={Spot}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="DetailsSpot"
|
||||||
|
component={SpotDetailsPage}
|
||||||
|
/>
|
||||||
|
{/* <Stack.Screen
|
||||||
|
name="DetailsSpot"
|
||||||
|
component={SpotDetailsPage}
|
||||||
|
sharedElements={(route) => {
|
||||||
|
return [route.params.spot.name]
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
|
</Stack.Navigator>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
// export const UserLogin = (username: string, password: string) => {
|
||||||
|
// return {
|
||||||
|
// type: userTypes.LOGIN,
|
||||||
|
// playload : username, password
|
||||||
|
// };
|
||||||
|
// }
|
@ -0,0 +1,17 @@
|
|||||||
|
import Music from "../../Model/Music";
|
||||||
|
import { Spot } from "../../Model/Spot";
|
||||||
|
import {spotTypes} from "../types/spotTypes";
|
||||||
|
|
||||||
|
export const setSpotList = (spotList: Spot[]) => {
|
||||||
|
return {
|
||||||
|
type: spotTypes.FETCH_SPOT,
|
||||||
|
playload: spotList,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setUserCurrentMusic = (currentMusic: Music) => {
|
||||||
|
return {
|
||||||
|
type: spotTypes.FETCH_SPOT,
|
||||||
|
playload: currentMusic,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
import { userTypes } from "../types/userTypes";
|
||||||
|
|
||||||
|
|
||||||
|
export interface Credentials {
|
||||||
|
email : string,
|
||||||
|
password : string
|
||||||
|
}
|
||||||
|
export interface CredentialsRegister {
|
||||||
|
email : string,
|
||||||
|
password : string,
|
||||||
|
name : string,
|
||||||
|
idFlad : string,
|
||||||
|
idSpotify : string
|
||||||
|
}
|
||||||
|
// export const setLoggedInState = loggedInState => (
|
||||||
|
// {
|
||||||
|
// type: types.SET_LOGGED_IN_STATE,
|
||||||
|
// loggedInState,
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
export const setLoginState = (cred : Credentials) => {
|
||||||
|
return {
|
||||||
|
type: userTypes.LOGIN,
|
||||||
|
playload : cred
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const restoreToken = (token : string) => {
|
||||||
|
return {
|
||||||
|
type: userTypes.RESTORE_TOKEN,
|
||||||
|
playload : token
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// export const UserLogin = (username: string, password: string) => {
|
||||||
|
// return {
|
||||||
|
// type: userTypes.LOGIN,
|
||||||
|
// playload : username, password
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
export const UserLogout = () => {
|
||||||
|
return {
|
||||||
|
type: userTypes.USER_LOGOUT,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
|||||||
|
import Music from "../../Model/Music";
|
||||||
|
import { Spot } from "../../Model/Spot";
|
||||||
|
import { discoveriesTypes } from "../types/discoverieTypes";
|
||||||
|
import { favoritesTypes } from "../types/favoritesTypes";
|
||||||
|
import { spotifyTypes } from "../types/spotifyTypes";
|
||||||
|
import { spotTypes } from "../types/spotTypes";
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
spot: [] as Spot[],
|
||||||
|
favoriteMusic: [] as Music [],
|
||||||
|
userCurrentMusic : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const appReducer = (state = initialState, action : any) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case favoritesTypes.ADD_FAVORITE_MUSICS:
|
||||||
|
return {...state, favoriteMusic: state.favoriteMusic.push(action.payload)};
|
||||||
|
case favoritesTypes.REMOVE_FAVORITE_MUSICS:
|
||||||
|
return {...state, favoriteMusic: state.favoriteMusic};
|
||||||
|
case spotTypes.FETCH_SPOT:
|
||||||
|
return {...state, spot: action.payload};
|
||||||
|
case discoveriesTypes.FETCH_DISCOVERIES:
|
||||||
|
return;
|
||||||
|
case spotifyTypes.GET_USER_CURRENT_MUSIC:
|
||||||
|
return {...state, userCurrentMusic: action.payload};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default appReducer
|
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
Uri getApiUrlAuthorize() => _api.identification.urlAuthorize;
|
||||||
|
|
||||||
|
String getApiRedirectUrl() => _api.identification.redirectUri;
|
||||||
|
|
||||||
|
String getIdSpotify() => _currentUser.idSpotify;
|
||||||
|
|
||||||
|
String getIdDafl() => _currentUser.idDafl;
|
||||||
|
|
||||||
|
|
||||||
|
case getCompleteMusic
|
||||||
|
|
||||||
|
playTrack(String id)
|
||||||
|
|
||||||
|
case addToPlaylist:
|
||||||
|
return {...state, spot: action.payload}
|
||||||
|
|
||||||
|
case removeFromPlaylist:
|
@ -0,0 +1,54 @@
|
|||||||
|
import { User } from "../../Model/User";
|
||||||
|
import { userTypes } from "../types/userTypes";
|
||||||
|
const initialState = {
|
||||||
|
loading: false,
|
||||||
|
user: User, // for user object
|
||||||
|
userFladToken: null, // for storing the JWT
|
||||||
|
userSpotifyToken : null,
|
||||||
|
error: null,
|
||||||
|
isLogedIn: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const userReducer = (state = initialState, action : any) => {
|
||||||
|
switch (action.type) {
|
||||||
|
// just for the navigation and speciafly use
|
||||||
|
// and
|
||||||
|
case userTypes.RESTORE_TOKEN:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
userFladToken : action.playload,
|
||||||
|
loading: true,
|
||||||
|
// isLogedIn: true,
|
||||||
|
};
|
||||||
|
case userTypes.LOGIN:
|
||||||
|
console.log("++++++++++++++++++++++++++++++++++++++userRducer+++++++++++++++++++++++++++++3");
|
||||||
|
console.log(action.playload, "LOOGGIIINN");
|
||||||
|
console.log("++++++++++++++++++++++++++++++++++++++userRducer+++++++++++++++++++++++++++++3");
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
user :action.playload,
|
||||||
|
isLogedIn: true
|
||||||
|
};
|
||||||
|
case userTypes.SIGNUP:
|
||||||
|
console.log("++++++++++++++++++++++++++++++++++++++userRducer+++++++++++++++++++++++++++++3");
|
||||||
|
|
||||||
|
console.log(action.playload, "LOOGGIIINN");
|
||||||
|
console.log("++++++++++++++++++++++++++++++++++++++userRducer+++++++++++++++++++++++++++++3");
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
user :action.playload,
|
||||||
|
isLogedIn: true
|
||||||
|
};
|
||||||
|
// case USER_SIGNUP:
|
||||||
|
// return {...state, nounours: action.payload};
|
||||||
|
case userTypes.USER_LOGOUT:
|
||||||
|
return {...state,
|
||||||
|
user :null,
|
||||||
|
isLogedIn: false }
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default userReducer
|
||||||
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
|||||||
|
import {configureStore} from '@reduxjs/toolkit'
|
||||||
|
import appReducer from './reducers/appReducer';
|
||||||
|
import userReducer from './reducers/userReducer';
|
||||||
|
|
||||||
|
// Reference here all your application reducers
|
||||||
|
const reducer = {
|
||||||
|
// appReducer: appReducer,
|
||||||
|
userReducer: userReducer
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = configureStore({
|
||||||
|
reducer : reducer,
|
||||||
|
},);
|
||||||
|
|
||||||
|
export default store;
|
@ -0,0 +1,150 @@
|
|||||||
|
//Define your action creators that will be responsible for asynchronous operations
|
||||||
|
|
||||||
|
import axios from "axios";
|
||||||
|
import { json } from "express";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { API_URL } from "../../fladConfig";
|
||||||
|
import { Credentials, CredentialsRegister, restoreToken, setLoginState } from "../actions/userActions";
|
||||||
|
import * as SecureStore from 'expo-secure-store';
|
||||||
|
import { User } from "../../Model/User";
|
||||||
|
import { UserFactory } from "../../Model/factory/UserFactory";
|
||||||
|
|
||||||
|
const key = 'userToken';
|
||||||
|
|
||||||
|
export const registerUser = ( resgisterCredential : CredentialsRegister) => {
|
||||||
|
//@ts-ignore
|
||||||
|
return async dispatch => {
|
||||||
|
try {
|
||||||
|
console.log(resgisterCredential);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const resp = await axios.post(
|
||||||
|
`${API_URL}/api/users/register`,
|
||||||
|
resgisterCredential,
|
||||||
|
config
|
||||||
|
)
|
||||||
|
|
||||||
|
if (resp.data.token) {
|
||||||
|
console.log(resp.data.token);
|
||||||
|
const token = resp.data.token;
|
||||||
|
// await SecureStore.setItemAsync(key, token);
|
||||||
|
const headers = {
|
||||||
|
'Authorization': 'Bearer ' + token};
|
||||||
|
const user = await axios.get(
|
||||||
|
"https://flad-api-production.up.railway.app/api/users",
|
||||||
|
{headers}
|
||||||
|
)
|
||||||
|
dispatch(setLoginState( UserFactory.JsonToModel(user.data) )); // our action is called here
|
||||||
|
// console.log(user.data);
|
||||||
|
// dispatch(setLoginState(user.data) ); // our action is called here
|
||||||
|
} else {
|
||||||
|
console.log('Login Failed', 'Username or Password is incorrect');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// if (resp.data.msg === 'success') { // response success checking logic could differ
|
||||||
|
// await SecureStore.setItemAsync(key, resp.data.token);
|
||||||
|
// dispatch(setLoginState(resp.data.user) ); // our action is called here
|
||||||
|
// } else {
|
||||||
|
// console.log('Login Failed', 'Username or Password is incorrect');
|
||||||
|
// }
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Error---------', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const userLogin = ( loginCredential : Credentials) => {
|
||||||
|
//@ts-ignore
|
||||||
|
return async dispatch => {
|
||||||
|
try {
|
||||||
|
console.log(loginCredential);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// const resppp = await axios.get(`${API_URL}/toto`);
|
||||||
|
// console.log(resppp.data, "sddsd");
|
||||||
|
|
||||||
|
const resp = await axios.post(
|
||||||
|
"https://flad-api-production.up.railway.app/api/users/login",
|
||||||
|
loginCredential,
|
||||||
|
config
|
||||||
|
)
|
||||||
|
console.log("====================================================================================")
|
||||||
|
console.log(resp.data)
|
||||||
|
console.log("====================================================================================")
|
||||||
|
if (resp.data.token) {
|
||||||
|
console.log(resp.data.token);
|
||||||
|
const token = resp.data.token;
|
||||||
|
await SecureStore.setItemAsync(key, token);
|
||||||
|
const headers = {
|
||||||
|
'Authorization': 'Bearer ' + token};
|
||||||
|
|
||||||
|
const user = await axios.get(
|
||||||
|
"https://flad-api-production.up.railway.app/api/users",
|
||||||
|
{headers}
|
||||||
|
)
|
||||||
|
// dispatch(setLoginState(resp.data.user) ); // our action is called here
|
||||||
|
console.log(user.data);
|
||||||
|
dispatch(setLoginState(user.data) ); // our action is called here
|
||||||
|
} else {
|
||||||
|
console.log('Login Failed', 'Username or Password is incorrect');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Error---------', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getRefreshToken = () => {
|
||||||
|
//@ts-ignore
|
||||||
|
return async dispatch => {
|
||||||
|
try {
|
||||||
|
let userToken : string | null = await SecureStore.getItemAsync(key);
|
||||||
|
console.log("==========key ==================");
|
||||||
|
console.log(userToken);
|
||||||
|
console.log("==========key ==================");
|
||||||
|
|
||||||
|
if (userToken) {
|
||||||
|
console.log("==========key2 ==================");
|
||||||
|
console.log(userToken);
|
||||||
|
console.log("==========key ==================");
|
||||||
|
|
||||||
|
dispatch(restoreToken(userToken) );
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.log("==========OOOOOORRRRRRRRHHHHHHHHHH ==================");
|
||||||
|
const empty = "";
|
||||||
|
dispatch(restoreToken(empty) );
|
||||||
|
|
||||||
|
console.log("merddee");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Error---------', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const logIn = (email, password) => {
|
||||||
|
// const action = (dispatch) => {
|
||||||
|
// if (email === user.email && password === user.password) {
|
||||||
|
// dispatch(setLoggedInState(true));
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// dispatch(setLoggedInState(false));
|
||||||
|
// return false;
|
||||||
|
// };
|
||||||
|
// return action;
|
||||||
|
// };
|
||||||
|
// better
|
||||||
|
async function save(key : string, value : string) {
|
||||||
|
await SecureStore.setItemAsync(key, value);
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
//Define your action creators that will be responsible for asynchronous operations
|
||||||
|
|
||||||
|
import axios from "axios";
|
||||||
|
import { API_URL } from "../../fladConfig";
|
||||||
|
import { RequestHandler } from "../../services/spotify/spotifyRequestHandler/utils";
|
||||||
|
import * as SecureStore from 'expo-secure-store';
|
||||||
|
import { Spot } from "../../Model/Spot";
|
||||||
|
import SpotifyService from "../../services/spotify/spotify.service";
|
||||||
|
import * as Location from 'expo-location';
|
||||||
|
import { setSpotList, setUserCurrentMusic } from "../actions/spotActions";
|
||||||
|
const key = 'userToken';
|
||||||
|
|
||||||
|
export type CreateSpotReqBody = {
|
||||||
|
id : string;
|
||||||
|
name : string;
|
||||||
|
artist : string;
|
||||||
|
linkCover : string;
|
||||||
|
user : string;
|
||||||
|
}
|
||||||
|
export const getSpotList = (resuestHandler : SpotifyService) => {
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
return async dispatch => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// let { status } = await Location.requestForegroundPermissionsAsync();
|
||||||
|
// if (status !== 'granted') {
|
||||||
|
// setErrorMsg('Permission to access location was denied');
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let location = await Location.getCurrentPositionAsync({});
|
||||||
|
// setLocation(location);
|
||||||
|
// const actualUser = MyApp.controller.getIdDafl();
|
||||||
|
// const actualSong = MyApp.controller.getCurrentMusic().id;
|
||||||
|
// const current = await new Promise<Position>((resolve, reject) => {
|
||||||
|
// Geolocation.getCurrentPosition(resolve, reject);
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
var userToken : string = await SecureStore.getItemAsync(key);
|
||||||
|
const headers = {
|
||||||
|
'Authorization': 'Bearer ' + userToken};
|
||||||
|
const data = await axios.get(
|
||||||
|
"https://flad-api-production.up.railway.app/api/users/nextTo",
|
||||||
|
{headers}
|
||||||
|
)
|
||||||
|
if (data.data.token) {
|
||||||
|
const spotsData: { [userId: string]: string } = {};
|
||||||
|
|
||||||
|
for (const item of data.data) {
|
||||||
|
spotsData[item.user] = item.music;
|
||||||
|
}
|
||||||
|
const spots = await Promise.all(
|
||||||
|
Object.entries(spotsData).map(async ([userId, value]) => {
|
||||||
|
const completeMusic = await resuestHandler.getMusicById(value);
|
||||||
|
return new Spot(userId, completeMusic);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch(setSpotList(spots)); // our action is called here
|
||||||
|
} else {
|
||||||
|
console.log('Login Failed', 'Username or Password is incorrect');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Error---------', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const getCurrentUserMusic = (resuestHandler : SpotifyService)=> {
|
||||||
|
//@ts-ignore
|
||||||
|
return async dispatch => {
|
||||||
|
try {
|
||||||
|
//@ts-ignore
|
||||||
|
var currentTrackResponse = await resuestHandler.getUserCurrentMusic();
|
||||||
|
if (!currentTrackResponse){
|
||||||
|
const recentlyTrackResponse = await resuestHandler.getUserRecentlyPlayedMusic();
|
||||||
|
if(!recentlyTrackResponse){
|
||||||
|
throw new Error;
|
||||||
|
}else{
|
||||||
|
currentTrackResponse = recentlyTrackResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const completeMusic = await resuestHandler.getMusicById(currentTrackResponse);
|
||||||
|
dispatch(setUserCurrentMusic(completeMusic));
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log('Error---------', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// export const getSpotList = () => {
|
||||||
|
// return async dispatch => {
|
||||||
|
// try {
|
||||||
|
|
||||||
|
// const spotPromise = await fetch(`${API_URL}/spotify/spot`);
|
||||||
|
// const spotJson = await spotPromise.json();
|
||||||
|
// const spotList: Spot[] = spotJson.map(spot => {
|
||||||
|
|
||||||
|
// } );
|
||||||
|
|
||||||
|
// dispatch(setNounoursList(spotList));
|
||||||
|
// } catch (error) {
|
||||||
|
// console.log('Error---------', error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
@ -0,0 +1,3 @@
|
|||||||
|
export const discoveriesTypes = {
|
||||||
|
FETCH_DISCOVERIES : 'FETCH_DISCOVERIES',
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export const favoritesTypes = {
|
||||||
|
ADD_FAVORITE_MUSICS : 'ADD_FAVORITE_MUSICS',
|
||||||
|
REMOVE_FAVORITE_MUSICS : 'REMOVE_FAVORITE_MUSICS',
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
export const playlistTypes = {
|
||||||
|
FETCH_USER_PLAYLISTS : 'FETCH_SPOT',
|
||||||
|
SAVE_IN_FLAD_PLAYLIST : 'SAVE_IN_FLAD_PLAYLIST',
|
||||||
|
FETCH_FLAD_PLAYLIST : 'FETCH_SPOT',
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
export const spotTypes = {
|
||||||
|
FETCH_SPOT : 'FETCH_SPOT',
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
export const spotifyTypes = {
|
||||||
|
GET_USER_CURRENT_MUSIC : 'GET_USER_CURRENT_MUSIC',
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
export const userTypes = {
|
||||||
|
LOGIN: 'LOGIN',
|
||||||
|
SIGNUP: 'SIGNUP',
|
||||||
|
UPDATE_USER: 'UPDATE_USER',
|
||||||
|
UPDATE_PROFILE_PICTURE: 'UPDATE_PROFILE_PICTURE',
|
||||||
|
USER_LOGOUT : 'USER_LOGOUT',
|
||||||
|
RESTORE_TOKEN : "RESTORE_TOKEN"
|
||||||
|
}
|
@ -0,0 +1,294 @@
|
|||||||
|
import { SharedElement } from "react-navigation-shared-element";
|
||||||
|
import { NavigationProp, RouteProp } from "@react-navigation/native";
|
||||||
|
import { View,Text,Image,StyleSheet, Dimensions, useWindowDimensions, Button, TouchableOpacity } from "react-native";
|
||||||
|
import Animated, { interpolate, SensorType, useAnimatedSensor, useAnimatedStyle, useDerivedValue, useSharedValue, Value, withSpring, withTiming } from "react-native-reanimated";
|
||||||
|
import { BlurView } from 'expo-blur';
|
||||||
|
import qs from "qs";
|
||||||
|
import axios from "axios";
|
||||||
|
import { Buffer } from 'buffer';
|
||||||
|
import { Audio } from 'expo-av';
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { State, TapGestureHandler } from "react-native-gesture-handler";
|
||||||
|
import { RequestHandler } from "../services/spotify/spotifyRequestHandler/utils";
|
||||||
|
import { FetchRequest } from "expo-auth-session/build/Fetch";
|
||||||
|
|
||||||
|
interface SpotProps {
|
||||||
|
spot: { name: string, sourceUrl: string, index : number };
|
||||||
|
}
|
||||||
|
const halfPi = Math.PI/2;
|
||||||
|
|
||||||
|
// const {width : wWidht} = Dimensions.get("window");
|
||||||
|
//@ts-ignore
|
||||||
|
const SpotDetailsPage = ({ route }) => {
|
||||||
|
const {width, height} = useWindowDimensions();
|
||||||
|
console.log(route);
|
||||||
|
|
||||||
|
const spot : { name: string, sourceUrl: string, index : number } = route.params.spot;
|
||||||
|
const [currentspot, setCurrentspot] = useState(spot);
|
||||||
|
const [sound, setSound] = useState(null);
|
||||||
|
|
||||||
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
|
const loader = useSharedValue(0);
|
||||||
|
useEffect(() => {
|
||||||
|
loader.value = isPlaying ? 1 : 0
|
||||||
|
}, [isPlaying,loader ]);
|
||||||
|
|
||||||
|
const transition = useDerivedValue(()=>{
|
||||||
|
return withTiming(loader.value, {duration : 1000})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// const styleAniamatedButton = useAnimatedStyle(() => {
|
||||||
|
// const verticalAxis =interpolate(
|
||||||
|
// transition.value,
|
||||||
|
// [0,1],
|
||||||
|
// [circumference, 0]
|
||||||
|
// )
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// top : withSpring( verticalAxis),
|
||||||
|
// left : withSpring(horizontalAxis),
|
||||||
|
// };
|
||||||
|
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
const trackPreviewUrl = 'https://p.scdn.co/mp3-preview/08ef3b9d6dbd6bab233f5e9ca564091902767f71?cid=774b29d4f13844c495f206cafdad9c86';
|
||||||
|
const playTrackPreview = async () => {
|
||||||
|
console.log("===============================================================================================================");
|
||||||
|
|
||||||
|
console.log('get in Sound');
|
||||||
|
|
||||||
|
const { sound } = await Audio.Sound.createAsync({uri :trackPreviewUrl});
|
||||||
|
//@ts-ignore
|
||||||
|
setSound(sound);
|
||||||
|
console.log('Playing Sound');
|
||||||
|
await sound.playAsync();
|
||||||
|
setIsPlaying(true);
|
||||||
|
|
||||||
|
|
||||||
|
// const soundObject = new Audio.Sound();
|
||||||
|
// try {
|
||||||
|
// await soundObject.loadAsync({ uri: trackPreviewUrl });
|
||||||
|
// await soundObject.playAsync();
|
||||||
|
// setIsPlaying(true);
|
||||||
|
// } catch (error) {
|
||||||
|
// console.log('Error loading sound:', error);
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePlaySound = async () => {
|
||||||
|
if (sound === null) {
|
||||||
|
const { sound: newSound } = await Audio.Sound.createAsync(
|
||||||
|
{ uri: trackPreviewUrl },
|
||||||
|
{ shouldPlay: true }
|
||||||
|
);
|
||||||
|
setSound(newSound);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//@ts-ignore
|
||||||
|
await sound.playAsync();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStopSound = async () => {
|
||||||
|
if (sound !== null) {
|
||||||
|
//@ts-ignore
|
||||||
|
await sound.stopAsync();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
setIsPlaying(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
return sound ? () => {
|
||||||
|
console.log('Unloading Sound');
|
||||||
|
//@ts-ignore
|
||||||
|
sound.unloadAsync();
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
}, [sound]);
|
||||||
|
// useEffect(() => {
|
||||||
|
// if(isPlaying){
|
||||||
|
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
console.log(spot);
|
||||||
|
const sensor = useAnimatedSensor(SensorType.ROTATION);
|
||||||
|
const styleAniamatedImage = useAnimatedStyle(() => {
|
||||||
|
const {yaw, pitch, roll} = sensor.sensor.value;
|
||||||
|
const verticalAxis =interpolate(
|
||||||
|
pitch,
|
||||||
|
[-halfPi,halfPi],
|
||||||
|
[-45, 45]
|
||||||
|
)
|
||||||
|
const horizontalAxis =interpolate(
|
||||||
|
roll,
|
||||||
|
[-halfPi*2,halfPi*2],
|
||||||
|
[-45, 45]
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
top : withSpring( verticalAxis),
|
||||||
|
left : withSpring(horizontalAxis),
|
||||||
|
};
|
||||||
|
|
||||||
|
})
|
||||||
|
const CLIENT_ID = "1f1e34e4b6ba48b388469dba80202b10";
|
||||||
|
const CLIENT_SECRET = "779371c6d4994a68b8dd6e84b0873c82";
|
||||||
|
const spotify = "BQA2IAFZ-7ta4-_4_Uqdcdrqi_peE6Hlf1jwxFqjXTbwes0z8xgVGx0rE3zv4cQlusd1ILJhRwkxzPsL1YakzSvCxaTI1P7kOzBrrMqlkDgk4vlFvzLjScB0hBLULbpZyn3ylgx4RyZBEWfmc24wZPQOsrJU58AYCveA52UxYVSIc_Frr7LZyRmwjzGB68MPZeBD"
|
||||||
|
var authOptions = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://api.spotify.com/v1/me/player/currently-playing',
|
||||||
|
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + spotify,
|
||||||
|
'Content-Type' : 'application/json',
|
||||||
|
'market' : 'FR',
|
||||||
|
},
|
||||||
|
json: true
|
||||||
|
};
|
||||||
|
|
||||||
|
var id = '0cFS3AMF9Lhj3CNoFvwjvY'
|
||||||
|
const requestor = new RequestHandler()
|
||||||
|
|
||||||
|
const getCurrentTrack = async () => {
|
||||||
|
try {
|
||||||
|
const opt : FetchRequest ={headers : Record}
|
||||||
|
requestor.spotifyFetch(`tracks${id}`,)
|
||||||
|
|
||||||
|
// var GetTrackOptions = {
|
||||||
|
// method: 'GET',
|
||||||
|
// url: 'https://api.spotify.com/v1/tracks/'+id,
|
||||||
|
|
||||||
|
// headers: {
|
||||||
|
// 'Authorization': 'Bearer ' + spotify,
|
||||||
|
// 'Content-Type' : 'application/json',
|
||||||
|
// 'market' : 'FR',
|
||||||
|
// },
|
||||||
|
// json: true
|
||||||
|
// };
|
||||||
|
// const resp = await axios(GetTrackOptions)
|
||||||
|
// console.log("============");
|
||||||
|
// console.log(resp.data.href);
|
||||||
|
// console.log("================================"+resp.data.album.images[0].url+ "================================");
|
||||||
|
// var tmp = currentspot;
|
||||||
|
|
||||||
|
// tmp.sourceUrl = resp.data.album.images[0].url;
|
||||||
|
// setCurrentspot(tmp);
|
||||||
|
// await axios(authOptions).then(async (response) =>{
|
||||||
|
// console.log(response.data.item.preview_url);
|
||||||
|
// const id = response.data.item.id;
|
||||||
|
// var GetTrackOptions = {
|
||||||
|
// method: 'GET',
|
||||||
|
// url: 'https://api.spotify.com/v1/tracks/'+id,
|
||||||
|
|
||||||
|
// headers: {
|
||||||
|
// 'Authorization': 'Bearer ' + spotify,
|
||||||
|
// 'Content-Type' : 'application/json',
|
||||||
|
// 'market' : 'FR',
|
||||||
|
// },
|
||||||
|
// json: true
|
||||||
|
// };
|
||||||
|
// console.log("============");
|
||||||
|
// const music = await axios(GetTrackOptions);
|
||||||
|
// console.log("================================"+music.data+ "================================");
|
||||||
|
// currentspot.sourceUrl = music.data.images[0];
|
||||||
|
// setCurrentspot(currentspot);
|
||||||
|
// })
|
||||||
|
|
||||||
|
// const response = await fetch('https://api.spotify.com/v1/me', {
|
||||||
|
// method: 'GET',
|
||||||
|
// headers: {
|
||||||
|
// Authorization: 'Bearer ' + spotify,
|
||||||
|
// 'Content-Type': 'application/json',
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// response.json()
|
||||||
|
|
||||||
|
// destructure the response and rename the properties to be in camelCase to satisfy my linter ;)
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const animationState = new Value(State.UNDETERMINED);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1, justifyContent : 'flex-start', alignItems : 'center' }}>
|
||||||
|
{/* <SharedElement id={spot.name} style={{ flex: 1 }}> */}
|
||||||
|
<View style={{borderWidth : 1, borderColor : 'red'}}>
|
||||||
|
|
||||||
|
<Animated.Image
|
||||||
|
source={{
|
||||||
|
uri:currentspot.sourceUrl ,
|
||||||
|
}}
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
|
||||||
|
width: 370,
|
||||||
|
height: 370,
|
||||||
|
borderRadius : 24,
|
||||||
|
resizeMode: 'stretch',
|
||||||
|
},styleAniamatedImage
|
||||||
|
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Button title="Current Track"
|
||||||
|
onPress={() => {
|
||||||
|
getCurrentTrack()
|
||||||
|
// promptAsync();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
{/* Button */}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{/* <TapGestureHandler {...gestureHandler}> */}
|
||||||
|
<Animated.View>
|
||||||
|
<TouchableOpacity style={{
|
||||||
|
backgroundColor: '#1DB954',
|
||||||
|
paddingVertical: 12,
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
borderRadius: 24,
|
||||||
|
}} onPressIn={handlePlaySound}
|
||||||
|
onPressOut={handleStopSound}
|
||||||
|
onLongPress={handlePlaySound}
|
||||||
|
delayLongPress={1000}>
|
||||||
|
|
||||||
|
<Text style={ {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold',}}>
|
||||||
|
{isPlaying ? 'Playing...' : 'Play'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
|
{/* </TapGestureHandler> */}
|
||||||
|
|
||||||
|
{/* Button */}
|
||||||
|
|
||||||
|
|
||||||
|
{/* <View style={detailRadicalStyle.container}>
|
||||||
|
<Text style={detailRadicalStyle.radicalText}>{props.character}</Text>
|
||||||
|
<SvgXml
|
||||||
|
xml={props.icon
|
||||||
|
.replace(/fill="#[0-9a-f]{6}"/g, `fill=${detailRadicalStyle.svg.color}`)}
|
||||||
|
width="30"
|
||||||
|
height="30"
|
||||||
|
opacity={0.5}
|
||||||
|
style={detailRadicalStyle.radicalIcon}
|
||||||
|
|
||||||
|
/>
|
||||||
|
</View> */}
|
||||||
|
{/* </SharedElement> */}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SpotDetailsPage;
|
@ -0,0 +1,4 @@
|
|||||||
|
interface IspotifyService {
|
||||||
|
getMusicById(idMusic : string): Promise<any>;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import axios, { AxiosError } from "axios";
|
||||||
|
|
||||||
|
export type Methods = 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
|
||||||
|
|
||||||
|
export interface FetchOptions {
|
||||||
|
/** The headers to apply. */
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
/** The method type. */
|
||||||
|
method?: Methods;
|
||||||
|
/** Search query parameters. */
|
||||||
|
params?: Record<string, any>;
|
||||||
|
/** The json body to send if available. */
|
||||||
|
body?: Record<string, string | boolean | number | (string | boolean | number)[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RequestHandler{
|
||||||
|
private _version: `v${number}` = 'v1';
|
||||||
|
get version(): string {
|
||||||
|
return this._version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async spotifyFetch(url: string, options: FetchOptions = {}, token: string) {
|
||||||
|
const resp = await axios({
|
||||||
|
url: `https://api.spotify.com/${this.version}${url}`,
|
||||||
|
method: options.method || 'GET',
|
||||||
|
params: options.params,
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer " + token,
|
||||||
|
Accept: 'application/json',
|
||||||
|
...options.headers
|
||||||
|
},
|
||||||
|
data: options.body
|
||||||
|
});
|
||||||
|
console.log()
|
||||||
|
return resp;
|
||||||
|
// if (
|
||||||
|
// // @ts-ignore
|
||||||
|
// error.response.data?.error?.message == "Invalid access token" ||
|
||||||
|
// // @ts-ignore
|
||||||
|
// error.response.data?.error?.message == "The access token expired" &&
|
||||||
|
// this.refreshMeta
|
||||||
|
// ) await this.refreshFromMeta();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue