add spotify authentication as a button to the app
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"scheme": "myapp",
|
||||
"scheme": "led.matrix",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"newArchEnabled": true,
|
||||
"ios": {
|
||||
|
||||
+31
-15
@@ -1,21 +1,37 @@
|
||||
import { Text, View, StyleSheet } from 'react-native';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
import React, {useState} from "react";
|
||||
import SpotifyAuthButton from "@/components/SpotifyAuthButton";
|
||||
import {Token} from "@/services/RestService";
|
||||
|
||||
export default function AboutScreen() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.text}>About screen</Text>
|
||||
</View>
|
||||
);
|
||||
const [token, setToken] = useState<Token | null>(null);
|
||||
|
||||
const handleAuthSuccess = (token: Token) => {
|
||||
setToken(token);
|
||||
console.log('Erhaltener Authentifizierungscode:', token);
|
||||
// Hier kannst du den Code weiterverwenden, um das Access Token zu erhalten
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.text}>About screen</Text>
|
||||
<Text>Willkommen bei der Spotify Authentifizierung</Text>
|
||||
<SpotifyAuthButton onAuthSuccess={handleAuthSuccess}/>
|
||||
{token && <Text>Erhaltener Code: {token.access_token}</Text>}
|
||||
</View>
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#25292e',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
text: {
|
||||
color: '#fff',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#25292e',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
text: {
|
||||
color: '#fff',
|
||||
},
|
||||
});
|
||||
|
||||
+49
-46
@@ -1,62 +1,65 @@
|
||||
import {ActivityIndicator, StyleSheet, Text, View} from 'react-native';
|
||||
import Button from '@/components/Button';
|
||||
import ImageViewer from '@/components/ImageViewer';
|
||||
|
||||
const PlaceholderImage = require('@/assets/images/GarfieldCharakter.webp');
|
||||
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
import {RestService} from '@/services/RestService';
|
||||
import useService from '@/hooks/useService';
|
||||
|
||||
const PlaceholderImage = require('@/assets/images/GarfieldCharakter.webp');
|
||||
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
|
||||
WebBrowser.maybeCompleteAuthSession();
|
||||
|
||||
export default function Index() {
|
||||
const {data, loading, error} = useService(RestService.fetchAllUser);
|
||||
const {data: usersData, loading: userLoading, error: userError} = useService(RestService.fetchAllUser);
|
||||
|
||||
const pickImageAsync = async () => {
|
||||
let result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ['images'],
|
||||
allowsEditing: true,
|
||||
quality: 1,
|
||||
});
|
||||
const pickImageAsync = async () => {
|
||||
let result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ['images'],
|
||||
allowsEditing: true,
|
||||
quality: 1,
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
console.log(result);
|
||||
} else {
|
||||
alert('You did not select any image.');
|
||||
}
|
||||
};
|
||||
if (!result.canceled) {
|
||||
console.log(result);
|
||||
} else {
|
||||
alert('You did not select any image.');
|
||||
}
|
||||
};
|
||||
|
||||
console.log(data);
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.imageContainer}>
|
||||
<ImageViewer imgSource={PlaceholderImage}/>
|
||||
console.log(usersData);
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.imageContainer}>
|
||||
<ImageViewer imgSource={PlaceholderImage}/>
|
||||
</View>
|
||||
<View style={styles.footerContainer}>
|
||||
{userLoading && <ActivityIndicator/>}
|
||||
{userError && <Text>Error: {userError.message}</Text>}
|
||||
{usersData && (
|
||||
<Text>
|
||||
{usersData.users.map((item) => item.name).join('; ')}
|
||||
</Text>
|
||||
)}
|
||||
<Button label="Choose a photo" theme="primary" onPress={pickImageAsync}/>
|
||||
<Button label="Use this photo"/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.footerContainer}>
|
||||
{loading && <ActivityIndicator/>}
|
||||
{error && <Text>Error: {error.message}</Text>}
|
||||
{data && (
|
||||
<Text>
|
||||
{data.users.map((item) => item.name).join('; ')}
|
||||
</Text>
|
||||
)}
|
||||
<Button label="Choose a photo" theme="primary" onPress={pickImageAsync}/>
|
||||
<Button label="Use this photo"/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#25292e',
|
||||
alignItems: 'center',
|
||||
},
|
||||
imageContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
footerContainer: {
|
||||
flex: 1 / 3,
|
||||
alignItems: 'center',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#25292e',
|
||||
alignItems: 'center',
|
||||
},
|
||||
imageContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
footerContainer: {
|
||||
flex: 1 / 3,
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import {Button} from 'react-native';
|
||||
import {useSpotifyAuth} from '@/hooks/useSpotifyAuth';
|
||||
import {Token} from "@/services/RestService";
|
||||
|
||||
interface SpotifyAuthButtonProps {
|
||||
onAuthSuccess: (token: Token) => void;
|
||||
}
|
||||
|
||||
const SpotifyAuthButton = ({onAuthSuccess}: SpotifyAuthButtonProps) => {
|
||||
const {promptAuth, isReady, error} = useSpotifyAuth(onAuthSuccess);
|
||||
|
||||
if (error) {
|
||||
console.error('Spotify Auth Error:', error);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
disabled={!isReady}
|
||||
title={'Login mit Spotify'}
|
||||
onPress={promptAuth}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpotifyAuthButton;
|
||||
+2
-3
@@ -2,7 +2,6 @@ import {useState, useEffect} from 'react';
|
||||
|
||||
type AsyncCallback<T> = () => Promise<T>;
|
||||
|
||||
|
||||
interface ServiceResult<T> {
|
||||
data: T | null,
|
||||
error: Error | null,
|
||||
@@ -18,7 +17,7 @@ const useService = <T>(callback: AsyncCallback<T>): ServiceResult<T> => {
|
||||
const executeCallback = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await callback(); // Führe den übergebenen Callback aus
|
||||
const result = await callback();
|
||||
setData(result);
|
||||
} catch (err) {
|
||||
setError(err as Error);
|
||||
@@ -33,4 +32,4 @@ const useService = <T>(callback: AsyncCallback<T>): ServiceResult<T> => {
|
||||
return {data, loading, error};
|
||||
}
|
||||
|
||||
export default useService;
|
||||
export default useService;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import {useEffect} from "react";
|
||||
import {makeRedirectUri, useAuthRequest} from "expo-auth-session";
|
||||
import {RestService, Token} from "@/services/RestService";
|
||||
|
||||
const discovery = {
|
||||
authorizationEndpoint: 'https://accounts.spotify.com/authorize',
|
||||
tokenEndpoint: 'https://accounts.spotify.com/api/token',
|
||||
};
|
||||
|
||||
interface UseSpotifyAuthResult {
|
||||
promptAuth: () => void;
|
||||
isReady: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
export const useSpotifyAuth = (
|
||||
onAuthSuccess: (token: Token) => void
|
||||
): UseSpotifyAuthResult => {
|
||||
const [request, response, promptAsync] = useAuthRequest(
|
||||
{
|
||||
clientId: process.env.EXPO_PUBLIC_SPOTIFY_CLIENT_ID!,
|
||||
scopes: ['user-read-currently-playing'],
|
||||
usePKCE: false,
|
||||
redirectUri: makeRedirectUri({
|
||||
scheme: 'led.matrix',
|
||||
path: 'callback',
|
||||
}),
|
||||
},
|
||||
discovery
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleAuthResponse = async () => {
|
||||
if (response?.type === 'success') {
|
||||
try {
|
||||
const {code} = response.params;
|
||||
const token = (await RestService.exchangeSpotifyCodeForToken(code)).token;
|
||||
console.log('Token:', token);
|
||||
onAuthSuccess(token);
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der Authentifizierung:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleAuthResponse();
|
||||
}, [response]);
|
||||
|
||||
return {
|
||||
promptAuth: () => promptAsync(),
|
||||
isReady: !!request,
|
||||
error: response?.type === 'error' ? new Error('Auth fehlgeschlagen') : null,
|
||||
};
|
||||
};
|
||||
Generated
+59
@@ -14,6 +14,7 @@
|
||||
"@react-navigation/native": "^7.0.0",
|
||||
"axios": "^1.7.7",
|
||||
"expo": "^52.0.10",
|
||||
"expo-auth-session": "^6.0.0",
|
||||
"expo-blur": "~14.0.1",
|
||||
"expo-constants": "~17.0.3",
|
||||
"expo-dev-client": "~5.0.4",
|
||||
@@ -7242,6 +7243,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/expo-application": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/expo-application/-/expo-application-6.0.1.tgz",
|
||||
"integrity": "sha512-w+1quSmKp8SYKT+GAFHSN5c6u+PqoVRIfpsLyRQrQdOnBA9dA8Hw6JT9sHNFmA30A2v1b/sdYZE3qKuRJFNSWQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"expo": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-asset": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-11.0.1.tgz",
|
||||
@@ -7259,6 +7269,24 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-auth-session": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/expo-auth-session/-/expo-auth-session-6.0.0.tgz",
|
||||
"integrity": "sha512-t40IvmUnWPdSFTr/d3FxDo3qbHdt6hPoRApZ9KH8/UoTjkdoSKnxi6W0/svpISDPMi25gB0lNYwy72YUisl1Yw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"expo-application": "~6.0.0",
|
||||
"expo-constants": "~17.0.0",
|
||||
"expo-crypto": "~14.0.0",
|
||||
"expo-linking": "~7.0.0",
|
||||
"expo-web-browser": "~14.0.0",
|
||||
"invariant": "^2.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-blur": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/expo-blur/-/expo-blur-14.0.1.tgz",
|
||||
@@ -7284,6 +7312,18 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-crypto": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-14.0.1.tgz",
|
||||
"integrity": "sha512-/gGpD9UAz8fgZtU08cwwqeQElkFmMy2Hc8lLa9laSjD3YN0XM07zDJyJ+CC1VhQ63G8WpUnq1IHSmaPbbLp+oQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-dev-client": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-5.0.4.tgz",
|
||||
@@ -12634,6 +12674,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-app-auth": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-app-auth/-/react-native-app-auth-8.0.0.tgz",
|
||||
"integrity": "sha512-uAlbR8ggVmFvH1zQwBWzksRTwpBMVIhAKsLhidwwlUH5idyTN4b4hJO7oDIa5FbvAAYcEZU5d0ChZDoIklomFw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"invariant": "2.2.4",
|
||||
"react-native-base64": "0.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-native": ">=0.63.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-base64": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-base64/-/react-native-base64-0.0.2.tgz",
|
||||
"integrity": "sha512-Fu/J1a2y0X22EJDWqJR2oEa1fpP4gTFjYxk8ElJdt1Yak3HOXmFJ7EohLVHU2DaQkgmKfw8qb7u/48gpzveRbg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-native-gesture-handler": {
|
||||
"version": "2.20.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.20.2.tgz",
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@react-navigation/native": "^7.0.0",
|
||||
"axios": "^1.7.7",
|
||||
"expo": "^52.0.10",
|
||||
"expo-auth-session": "^6.0.0",
|
||||
"expo-blur": "~14.0.1",
|
||||
"expo-constants": "~17.0.3",
|
||||
"expo-dev-client": "~5.0.4",
|
||||
|
||||
+79
-59
@@ -1,72 +1,92 @@
|
||||
import axios from 'axios';
|
||||
import {makeRedirectUri} from "expo-auth-session";
|
||||
import User from "@/model/User";
|
||||
|
||||
const API_URL = process.env.EXPO_PUBLIC_API_URL;
|
||||
const JWT_TOKEN = process.env.EXPO_PUBLIC_JWT_TOKEN;
|
||||
|
||||
export interface Token {
|
||||
access_token: string,
|
||||
refresh_token: string,
|
||||
expires_in: number,
|
||||
token_type: string,
|
||||
scope: string,
|
||||
|
||||
}
|
||||
|
||||
|
||||
const RestService = {
|
||||
fetchAllUser: async () => {
|
||||
try {
|
||||
const response = await axios.get<{ users: User[] }>(`${API_URL}/user`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${JWT_TOKEN}`,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
exchangeSpotifyCodeForToken: async (code: string) => {
|
||||
try {
|
||||
const redirectUri = makeRedirectUri({
|
||||
scheme: 'led.matrix',
|
||||
path: 'callback',
|
||||
});
|
||||
const response = await axios.get<{ token: Token }>(
|
||||
`${API_URL}/spotify/token/generate/code/${code}/redirect-uri/${encodeURIComponent(redirectUri)}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${JWT_TOKEN}`,
|
||||
}
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error exchanging Spotify code:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
getCurrentlyPlayingSong: async (accessToken: string) => {
|
||||
try {
|
||||
const response = await axios.get('https://api.spotify.com/v1/me/player/currently-playing', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
fetchAllUser: async () => {
|
||||
try {
|
||||
const response = await axios.get<{ users: User[] }>(`${API_URL}/user`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${JWT_TOKEN}`,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching users:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
sendPayloadToSocket: async (userId: string, payload: Object) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${API_URL}/websocket/send-message`,
|
||||
{users: [userId], payload},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${JWT_TOKEN}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
sendPayloadToSocket: async (userId: string, payload: object) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${API_URL}/websocket/send-message`,
|
||||
{users: [userId], payload},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${JWT_TOKEN}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error sending payload to socket:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
broadcast: async (payload: Object) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${API_URL}/websocket/broadcast`,
|
||||
{payload},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${JWT_TOKEN}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
broadcast: async (payload: object) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${API_URL}/websocket/broadcast`,
|
||||
{payload},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${JWT_TOKEN}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error broadcasting payload:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export {RestService};
|
||||
|
||||
Reference in New Issue
Block a user