diff --git a/app/(tabs)/modes/image.tsx b/app/(tabs)/modes/image.tsx index 9d05ae3..cbb9192 100644 --- a/app/(tabs)/modes/image.tsx +++ b/app/(tabs)/modes/image.tsx @@ -1,27 +1,290 @@ import ThemedHeader from "@/src/components/themed/ThemedHeader"; -import React from "react"; +import React, { useState } from "react"; import ThemedBackground from "@/src/components/themed/ThemedBackground"; import CustomImagePicker from "@/src/components/ImagePicker"; -import {ImagePickerSuccessResult} from "expo-image-picker"; +import { ImagePickerSuccessResult } from "expo-image-picker"; +import { View, StyleSheet, Alert, FlatList, Text, ActivityIndicator, TouchableOpacity, Linking, Platform } from "react-native"; +import { S3File, RestService } from "@/src/services/RestService"; +import ThemedButton from "@/src/components/themed/ThemedButton"; +import { useTheme } from "@/src/context/ThemeProvider"; +import { useAuth } from "@/src/context/AuthProvider"; +import { MaterialIcons } from '@expo/vector-icons'; export default function ImageScreen() { - const onSuccess = (result: ImagePickerSuccessResult) => { - console.log("Image picked successfully", result); - } + const { token } = useAuth(); + const [uploading, setUploading] = useState(false); + const [files, setFiles] = useState([]); + const [showFiles, setShowFiles] = useState(false); + const [loadingFiles, setLoadingFiles] = useState(false); + const { theme } = useTheme(); + + const { primary, onSurface, outline, error } = theme.colors; + + const fetchStoredFiles = async () => { + setLoadingFiles(true); + try { + const response = await new RestService(token).getStoredFiles(); + if (response.ok && response.data.files) { + setFiles(response.data.files); + setShowFiles(true); + } else { + Alert.alert("Fehler", "Dateien konnten nicht abgerufen werden"); + } + } catch (error) { + console.error("Fehler beim Abrufen der Dateien:", error); + Alert.alert("Fehler", "Dateien konnten nicht abgerufen werden"); + } finally { + setLoadingFiles(false); + } + }; + + const onSuccess = async (result: ImagePickerSuccessResult) => { + if (result.assets && result.assets.length > 0) { + const selectedAsset = result.assets[0]; + console.log("Image picked successfully", selectedAsset); + + try { + setUploading(true); + + const formData = new FormData(); + + const fileInfo = { + uri: selectedAsset.uri, + type: selectedAsset.mimeType || 'image/jpeg', + name: selectedAsset.fileName || 'upload.jpg' + }; + + // @ts-ignore + formData.append('image', fileInfo); + + const response = await new RestService(token).uploadFile(formData); + + if (response.ok) { + Alert.alert("Erfolg", "Datei erfolgreich hochgeladen"); + fetchStoredFiles(); + } else { + Alert.alert("Fehler", "Upload fehlgeschlagen"); + } + } catch (error) { + console.error("Fehler beim Hochladen der Datei:", error); + Alert.alert("Fehler", "Upload fehlgeschlagen"); + } finally { + setUploading(false); + } + } + }; + const onFailure = (error: Error) => { console.error("Error picking image", error); - } + Alert.alert("Fehler", "Bildauswahl fehlgeschlagen"); + }; const onCanceled = () => { console.log("Image picking canceled"); - } + }; + + const toggleFilesList = () => { + if (!showFiles) { + fetchStoredFiles(); + } else { + setShowFiles(false); + } + }; + + const formatDate = (dateString: Date) => { + const date = new Date(dateString); + return date.toLocaleString('de-DE'); + }; + + const formatFileSize = (size: number) => { + if (size < 1024) { + return `${size} B`; + } else if (size < 1024 * 1024) { + return `${(size / 1024).toFixed(2)} KB`; + } else { + return `${(size / (1024 * 1024)).toFixed(2)} MB`; + } + }; + + const viewFile = async (objectKey: string, fileName: string, mimeType: string) => { + try { + const response = await new RestService(token).getFileUrl(objectKey); + if (response.ok && response.data.url) { + const url = response.data.url; + + const canOpen = await Linking.canOpenURL(url); + if (canOpen) { + await Linking.openURL(url); + } else { + Alert.alert("Fehler", "Diese URL kann nicht geöffnet werden"); + } + } else { + Alert.alert("Fehler", "Datei-URL konnte nicht abgerufen werden"); + } + } catch (error) { + console.error("Fehler beim Abrufen der Datei-URL:", error); + Alert.alert("Fehler", "Datei-URL konnte nicht abgerufen werden"); + } + }; + + const deleteFile = async (objectKey: string) => { + Alert.alert( + "Datei löschen", + "Möchtest du diese Datei wirklich löschen?", + [ + { + text: "Abbrechen", + style: "cancel" + }, + { + text: "Löschen", + style: "destructive", + onPress: async () => { + try { + const response = await new RestService(token).deleteFile(objectKey); + if (response.ok) { + Alert.alert("Erfolg", "Datei erfolgreich gelöscht"); + // Liste aktualisieren + fetchStoredFiles(); + } else { + Alert.alert("Fehler", "Datei konnte nicht gelöscht werden"); + } + } catch (error) { + console.error("Fehler beim Löschen der Datei:", error); + Alert.alert("Fehler", "Datei konnte nicht gelöscht werden"); + } + } + } + ] + ); + }; return ( Bildschirm für Bildauswahl - + + + {uploading ? ( + + + + Datei wird hochgeladen... + + + ) : ( + + )} + + + + + + {showFiles && ( + + Gespeicherte Dateien + + {loadingFiles ? ( + + ) : files.length > 0 ? ( + item.key} + renderItem={({ item }) => ( + + + {item.originalName} + + + Typ: {item.mimeType} + + + Größe: {formatFileSize(item.size)} + + + Zuletzt geändert: {formatDate(item.lastModified)} + + + viewFile(item.key, item.originalName, item.mimeType)} + > + + + + deleteFile(item.key)} + > + + + + + )} + /> + ) : ( + + Keine Dateien gefunden + + )} + + )} + ); } + +const styles = StyleSheet.create({ + container: { + flex: 1, + width: '100%', + padding: 16, + }, + loadingContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + loadingText: { + marginTop: 10, + fontSize: 16, + }, + buttonContainer: { + marginVertical: 20, + alignItems: 'center', + }, + filesList: { + flex: 1, + width: '100%', + }, + fileItem: { + padding: 12, + marginVertical: 8, + borderWidth: 1, + borderRadius: 8, + position: 'relative', + }, + fileItemButtons: { + flexDirection: 'row', + position: 'absolute', + top: 12, + right: 12, + }, + fileButton: { + width: 36, + height: 36, + borderRadius: 18, + justifyContent: 'center', + alignItems: 'center', + marginLeft: 8, + } +}); diff --git a/src/services/RestService.ts b/src/services/RestService.ts index c71a8ff..d59f10f 100644 --- a/src/services/RestService.ts +++ b/src/services/RestService.ts @@ -18,6 +18,14 @@ export interface ApiResponse { data: T; } +export interface S3File { + key: string; + lastModified: Date; + originalName: string; + mimeType: string; + size: number; +} + class RestService { private readonly jwtToken: string | null; private api: AxiosInstance; @@ -148,6 +156,36 @@ class RestService { return this.request>('POST', '/auth/logout'); } + async uploadFile(file: FormData): Promise> { + return this.request>( + 'POST', + ' /storage/upload', + file, + {'Content-Type': 'multipart/form-data'} + ); + } + + async getStoredFiles(): Promise> { + return this.request>( + 'GET', + '/storage/files' + ); + } + + async getFileUrl(objectKey: string): Promise> { + return this.request>( + 'GET', + `/storage/files/${objectKey}/url` + ); + } + + async deleteFile(objectKey: string): Promise> { + return this.request>( + 'DELETE', + `/storage/file/${objectKey}` + ); + } + private async request(method: Method, url: string, data?: any, headers?: any): Promise { try { const response = await this.api.request({