diff --git a/src/FLAD/app.json b/src/FLAD/app.json index 906754f..86c13a7 100644 --- a/src/FLAD/app.json +++ b/src/FLAD/app.json @@ -19,7 +19,12 @@ "**/*" ], "ios": { - "supportsTablet": true + "supportsTablet": true, + "infoPlist": { + "UIBackgroundModes": [ + "audio" + ] + } }, "android": { "adaptiveIcon": { diff --git a/src/FLAD/components/Card.tsx b/src/FLAD/components/Card.tsx index 2623d4d..347da23 100644 --- a/src/FLAD/components/Card.tsx +++ b/src/FLAD/components/Card.tsx @@ -40,8 +40,8 @@ const Card = ({ title, image, onSwipe } : CardProps) => { }, onEnd : (event, context) => { - console.log(translateX.value); - + console.log(translateX.value - translateY.value); + // console.log(translateY.value); // translateX.value = withSpring(0); // translateY.value = withSpring(snapPoint(translateY.value,velocityY, snapPoints )) if (translateX.value > 160) { @@ -82,22 +82,26 @@ const Card = ({ title, image, onSwipe } : CardProps) => { const opacCStyle = useAnimatedStyle(() => { const opacityl = interpolate ( translateX.value, - [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 4], + [-SCREEN_WIDTH / 4, 0, SCREEN_WIDTH / 4], [0.35, 0, 0.35]); return { opacity : opacityl, }; }); + const opacDStyle = useAnimatedStyle(() => { const opacityl = interpolate ( translateY.value, - [-SCREEN_HEIGHT / 4, 0, SCREEN_HEIGHT / 2], - [0, 0, 1]); + [ 100, 300], + [ 0, 1]); + return { + backgroundColor : 'red', opacity : opacityl, }; }); + const horizontalThreshold = SCREEN_WIDTH * 0.65; const rotateStyle = useAnimatedStyle(() => { @@ -148,14 +152,14 @@ const horizontalThreshold = SCREEN_WIDTH * 0.65; ], }; }); - + console.log('==========================================',SCREEN_WIDTH/4 , "==============================="); return ( @@ -163,6 +167,7 @@ const horizontalThreshold = SCREEN_WIDTH * 0.65; , + radius : number; +} + +const PI= Math.PI; +const FladInput = ({background, foreground, progress} : CircularProps) => { + const [focused, setFocused] = useState(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 ( + <> + + + + + + + + + + + + + ); + }; + +const styles = StyleSheet.create({ + input : { + justifyContent : 'center', + alignItems : 'center', + placeholder : "placeholde" + }, +}) + +export default FladInput; \ No newline at end of file diff --git a/src/FLAD/components/FladLoadingScreen.tsx b/src/FLAD/components/FladLoadingScreen.tsx index 3bdeda1..6ff701e 100644 --- a/src/FLAD/components/FladLoadingScreen.tsx +++ b/src/FLAD/components/FladLoadingScreen.tsx @@ -79,7 +79,7 @@ const FladLoading = () => { - + {/* */} diff --git a/src/FLAD/components/HalfCircle.tsx b/src/FLAD/components/HalfCircle.tsx new file mode 100644 index 0000000..bde8742 --- /dev/null +++ b/src/FLAD/components/HalfCircle.tsx @@ -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(false); + + return ( + + + + + + ); + }; + +const styles = StyleSheet.create({ + input : { + justifyContent : 'center', + alignItems : 'center', + placeholder : "placeholde" + }, +}) + +export default HalfCirlce; \ No newline at end of file diff --git a/src/FLAD/package.json b/src/FLAD/package.json index 3edcc5e..0b23cee 100644 --- a/src/FLAD/package.json +++ b/src/FLAD/package.json @@ -19,6 +19,8 @@ "buffer": "^6.0.3", "expo": "~47.0.12", "expo-auth-session": "~3.8.0", + "expo-av": "~13.0.3", + "expo-blur": "~12.0.1", "expo-cli": "^6.3.0", "expo-haptics": "~12.0.1", "expo-linear-gradient": "~12.0.1", @@ -43,8 +45,7 @@ "react-navigation-shared-element": "^3.1.3", "react-redux": "^8.0.5", "redux": "^4.2.1", - "rive-react-native": "^3.0.41", - "expo-blur": "~12.0.1" + "rive-react-native": "^3.0.41" }, "devDependencies": { "@babel/core": "^7.12.9", diff --git a/src/FLAD/redux/actions/userActions.tsx b/src/FLAD/redux/actions/userActions.tsx index 35d78f6..f470e84 100644 --- a/src/FLAD/redux/actions/userActions.tsx +++ b/src/FLAD/redux/actions/userActions.tsx @@ -5,6 +5,13 @@ 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, diff --git a/src/FLAD/redux/thunk/authThunk.tsx b/src/FLAD/redux/thunk/authThunk.tsx index c658329..6e70aca 100644 --- a/src/FLAD/redux/thunk/authThunk.tsx +++ b/src/FLAD/redux/thunk/authThunk.tsx @@ -4,32 +4,53 @@ import axios from "axios"; import { json } from "express"; import { useEffect } from "react"; import { API_URL } from "../../fladConfig"; -import { Credentials, restoreToken, setLoginState } from "../actions/userActions"; +import { Credentials, CredentialsRegister, restoreToken, setLoginState } from "../actions/userActions"; import * as SecureStore from 'expo-secure-store'; const key = 'userToken'; -export const registerUser = ( resgisterCredential : any) => { +export const registerUser = ( resgisterCredential : CredentialsRegister) => { //@ts-ignore return async dispatch => { try { - const config = { - headers: { - 'Content-Type': 'application/json', - }, - } + 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.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 + + 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'); + 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); } diff --git a/src/FLAD/screens/InscriptionPage.tsx b/src/FLAD/screens/InscriptionPage.tsx index bae2a4a..365f255 100644 --- a/src/FLAD/screens/InscriptionPage.tsx +++ b/src/FLAD/screens/InscriptionPage.tsx @@ -7,6 +7,11 @@ import * as AuthSession from 'expo-auth-session'; import axios from 'axios'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest } from 'expo-auth-session'; +import { registerUser } from '../redux/thunk/authThunk'; +import { useDispatch } from 'react-redux'; +import { CredentialsRegister } from '../redux/actions/userActions'; +import { Buffer } from 'buffer'; + // @ts-ignore const DismissKeyboard = ({ children }) => ( Keyboard.dismiss()}> @@ -32,10 +37,16 @@ await SecureStore.setItemAsync(key, value); export default function InscriptionPage() { const [rememberMe, setRememberMe] = useState(false); const navigation = useNavigation(); + const [spotifyToken, setSpotifyToken] = useState(''); + const [spotifyID, setSpotifyIds] = useState('') + const [username, setUsername] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); const toggleRememberMe = () => { setRememberMe(!rememberMe); } + //spotify auth const [request, response, promptAsync] = useAuthRequest( { @@ -60,6 +71,68 @@ export default function InscriptionPage() { } }, [response]); + const dispatch = useDispatch(); + + const submitForm = () => { + const credentials: CredentialsRegister = { + email: email, + password: password, + idSpotify : spotifyToken, + name : username, + idFlad : "3030" + }; + //@ts-ignore + dispatch(registerUser(credentials)) + } + const scopesArr = ['user-read-private','user-read-email','user-read-playback-state','user-read-currently-playing','user-read-recently-played','playlist-modify-public','ugc-image-upload','user-modify-playback-state']; +const scopes = scopesArr.join(' '); + //work so use this for my implementation + const getAuthorizationCode = async () => { + try { + const redirectUrl = "https://auth.expo.io/@anonymous/FLAD-7eafd441-fd6b-4fb6-924c-ec2b0ed5ce6d"; //this will be something like https://auth.expo.io/@your-username/your-app-slug + const result = await AuthSession.startAsync({ + authUrl: + 'https://accounts.spotify.com/authorize' + + '?response_type=code' + + '&client_id=' + + "1f1e34e4b6ba48b388469dba80202b10" + + (scopes ? '&scope=' + encodeURIComponent(scopes) : '') + + '&redirect_uri=' + + encodeURIComponent(redirectUrl), + }) + console.log(result); + return result.params.code; + } catch (err) { + console.error(err) + } + } + const getTokens = async () => { + try { + const authorizationCode = await getAuthorizationCode() //we wrote this function above + console.log(authorizationCode, "shhhhhhhhhhhhhheeeeeeeeeeeeeeeetttttttttttt"); + const response = await fetch('https://accounts.spotify.com/api/token', { + method: 'POST', + headers: { + Authorization: 'Basic ' + (Buffer.from('1f1e34e4b6ba48b388469dba80202b10' + ':' + '779371c6d4994a68b8dd6e84b0873c82').toString('base64')), + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `grant_type=authorization_code&code=${authorizationCode}&redirect_uri=https://auth.expo.io/@anonymous/FLAD-7eafd441-fd6b-4fb6-924c-ec2b0ed5ce6d`, + }); + const responseJson = await response.json(); + console.log(responseJson, "okkkkkkkkkkkkkkk") ; + // destructure the response and rename the properties to be in camelCase to satisfy my linter ;) + const { + access_token: accessToken, + refresh_token: refreshToken, + expires_in: expiresIn, + } = responseJson; + await setSpotifyToken(accessToken); + console.log(spotifyToken); + } catch (err) { + console.error(err); + } + } + return ( @@ -70,24 +143,31 @@ export default function InscriptionPage() { S'INSCRIRE - + - + - + - { - promptAsync(); + { + await getTokens(); }} style={[styles.buttonSpotify, styles.shadow]}> Lier compte - console.log("Oui")}> + + submitForm()}> diff --git a/src/FLAD/screens/SpotDetailsPage.tsx b/src/FLAD/screens/SpotDetailsPage.tsx index 8987e0e..a57a37f 100644 --- a/src/FLAD/screens/SpotDetailsPage.tsx +++ b/src/FLAD/screens/SpotDetailsPage.tsx @@ -1,8 +1,14 @@ import { SharedElement } from "react-navigation-shared-element"; import { NavigationProp, RouteProp } from "@react-navigation/native"; -import { View,Text,Image,StyleSheet, Dimensions, useWindowDimensions } from "react-native"; -import Animated, { interpolate, SensorType, useAnimatedSensor, useAnimatedStyle, withSpring, withTiming } from "react-native-reanimated"; +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"; interface SpotProps { spot: { name: string, sourceUrl: string, index : number }; @@ -13,10 +19,100 @@ const halfPi = Math.PI/2; //@ts-ignore const SpotDetailsPage = ({ route }) => { const {width, height} = useWindowDimensions(); - console.log("===================================="); 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(() => { @@ -24,12 +120,12 @@ const SpotDetailsPage = ({ route }) => { const verticalAxis =interpolate( pitch, [-halfPi,halfPi], - [-30, 30] + [-45, 45] ) const horizontalAxis =interpolate( roll, [-halfPi*2,halfPi*2], - [-30, 30] + [-45, 45] ) return { top : withSpring( verticalAxis), @@ -37,24 +133,139 @@ const SpotDetailsPage = ({ route }) => { }; }) + 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 getCurrentTrack = async () => { + try { + 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 ( {/* */} + + +