Wysyłanie zdjęć na backend i ich odbieranie

This commit is contained in:
2025-05-23 15:13:31 +02:00
parent c6fbbe9222
commit 0ee2b8d702
4 changed files with 258 additions and 163 deletions

View File

@@ -6,15 +6,43 @@ export async function listLocations() {
try { try {
const response = await axios.get(`${API_URL}/locations/all`); const response = await axios.get(`${API_URL}/locations/all`);
if (!response) { if (!response) {
throw new Error("No locations found"); return "No locations found";
} }
return response.data;
// Нормализуем изображения в полученных данных
return response.data.map(location => ({
...location,
imageSource: normalizeImageSource(location.image)
}));
} catch (error) { } catch (error) {
console.error("Error fetching locations:", error); console.error("Error fetching locations:", error);
throw error; throw error;
} }
} }
const normalizeImageSource = (image) => {
if (!image) return null;
// Проверка, является ли изображение URL
if (typeof image === 'string') {
// Если строка начинается с http или https, это URL
if (image.startsWith('http://') || image.startsWith('https://')) {
return { uri: image };
}
// Проверка на base64
if (image.startsWith('data:image')) {
return { uri: image };
} else if (image.length > 100) {
// Предполагаем, что это base64 без префикса
return { uri: `data:image/jpeg;base64,${image}` };
}
}
// Возвращаем null для неправильного формата
return null;
}
export async function getLocation(id) { export async function getLocation(id) {
try { try {
const location = await axios.get(`${API_URL}/locations/${id}`); const location = await axios.get(`${API_URL}/locations/${id}`);
@@ -22,7 +50,7 @@ export async function getLocation(id) {
throw new Error("Location not found"); throw new Error("Location not found");
} }
return location.data; return [...location.data, {image: normalizeImageSource(location.image)}];
} catch (error) { } catch (error) {
console.error("Error fetching location:", error); console.error("Error fetching location:", error);
throw error; throw error;

View File

@@ -1,172 +1,239 @@
import { useState } from "react"; import {useState} from "react";
import { import {
StyleSheet, StyleSheet,
Platform, Platform,
KeyboardAvoidingView, KeyboardAvoidingView,
ScrollView, ScrollView,
Image, Image,
View,
} from "react-native"; } from "react-native";
import { TextInput, Button, Snackbar } from "react-native-paper"; import {TextInput, Button, Snackbar, Text} from "react-native-paper";
import { requestCameraPermissionsAsync, launchCameraAsync } from 'expo-image-picker'; import {requestCameraPermissionsAsync, launchCameraAsync} from 'expo-image-picker';
import useLocationStore from "@/locationStore"; import useLocationStore from "@/locationStore";
export default function FormScreen() { export default function FormScreen() {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: "",
description: "",
image: "",
area: 0,
population: 0,
});
const [message, setMessage] = useState("");
const [visible, setVisible] = useState(false);
const [picture, setPicture] = useState(null);
const addLocation = useLocationStore((state) => state.addLocation);
const handleAddLocation = async () => {
if (
formData.name &&
formData.description &&
formData.image &&
formData.area &&
formData.population
) {
const newLocation = {
id: Date.now(),
name: formData.name,
description: formData.description,
image: formData.image,
area: parseFloat(formData.area),
population: parseInt(formData.population),
};
const added = await addLocation(newLocation);
setFormData({
name: "", name: "",
description: "", description: "",
image: "", image: "",
area: 0, area: 0,
population: 0, population: 0,
});
if (added != null) {
setMessage("Lokalizacja została dodana!");
setVisible(true);
} else {
setMessage("Wystąpił błąd podczas dodawania lokalizacji!");
setVisible(true);
}
} else {
setMessage("Wypełnij wszystkie pola!");
setVisible(true);
}
};
const takePicture = async () => {
const {status} = await requestCameraPermissionsAsync();
if (status !== 'granted') {
return;
}
const result = await launchCameraAsync({
allowsEditing: false,
}); });
const [message, setMessage] = useState("");
const [visible, setVisible] = useState(false);
const [picture, setPicture] = useState(null);
const [imageMethod, setImageMethod] = useState(null); // null, 'link' или 'camera'
if (!result.canceled && result.assets && result.assets.length > 0) { const addLocation = useLocationStore((state) => state.addLocation);
setPicture(result.assets[0].uri);
const handleAddLocation = async () => {
if (
formData.name &&
formData.description &&
formData.image &&
formData.area &&
formData.population
) {
const newLocation = {
id: Date.now(),
name: formData.name,
description: formData.description,
image: formData.image,
area: parseFloat(formData.area),
population: parseInt(formData.population),
};
const added = await addLocation(newLocation);
setFormData({
name: "",
description: "",
image: "",
area: 0,
population: 0,
});
setPicture(null);
setImageMethod(null);
if (added != null) {
setMessage("Lokalizacja została dodana!");
setVisible(true);
} else {
setMessage("Wystąpił błąd podczas dodawania lokalizacji!");
setVisible(true);
}
} else {
setMessage("Wypełnij wszystkie pola!");
console.log(formData);
setVisible(true);
}
};
const takePicture = async () => {
const {status} = await requestCameraPermissionsAsync();
if (status !== 'granted') {
setMessage("Brak uprawnień do kamery!");
setVisible(true);
return;
}
const result = await launchCameraAsync({
allowsEditing: false,
base64: true,
});
if (!result.canceled && result.assets && result.assets.length > 0) {
const image = result.assets[0];
setPicture(image.uri);
console.log(image.base64);
setFormData({...formData, image: image.base64});
}
} }
}
return ( const selectImageMethod = (method) => {
<KeyboardAvoidingView setImageMethod(method);
style={{ flex: 1 }} if (method === 'camera') {
behavior={Platform.OS === "ios" ? "padding" : "height"} takePicture()
> } else {
<ScrollView contentContainerStyle={styles.container}> setFormData({...formData, image: ""});
<Snackbar setPicture(null);
visible={visible} }
onDismiss={() => setVisible(false)} }
duration={3000}
return (
<KeyboardAvoidingView
style={{flex: 1}}
behavior={Platform.OS === "ios" ? "padding" : "height"}
> >
{message} <ScrollView contentContainerStyle={styles.container}>
</Snackbar> <Snackbar
<TextInput visible={visible}
mode="outlined" onDismiss={() => setVisible(false)}
label="Nazwa" duration={3000}
placeholder="Wpisz nazwę" >
style={{ margin: 10, width: "100%" }} {message}
value={formData.name} </Snackbar>
onChangeText={(e) => setFormData({ ...formData, name: e })} <Text style={styles.label} variant="labelLarge">Jak chcesz dodać zdjęcie?</Text>
/> <View style={styles.imageMethodButtons}>
<TextInput <Button
mode="outlined" mode={imageMethod === 'link' ? "contained" : "outlined"}
label="Opis" onPress={() => selectImageMethod('link')}
placeholder="Wpisz opis" style={styles.methodButton}
style={{ margin: 10, width: "100%" }} >
multiline={true} Użyj linku
value={formData.description} </Button>
onChangeText={(e) => setFormData({ ...formData, description: e })} <Button
/> mode={imageMethod === 'camera' ? "contained" : "outlined"}
<TextInput onPress={() => selectImageMethod('camera')}
mode="outlined" style={styles.methodButton}
label="Link do zdjęcia" >
placeholder="Wpisz link do zdjęcia" Zrób zdjęcie
multiline={true} </Button>
style={{ margin: 10, width: "100%" }} </View>
value={formData.image}
onChangeText={(e) => setFormData({ ...formData, image: e })} {imageMethod === 'link' && (
/> <TextInput
<TextInput mode="outlined"
mode="outlined" label="Link do zdjęcia"
label="Powierzchnia" placeholder="Wpisz link do zdjęcia"
placeholder="Wpisz powierzchnię" multiline={true}
style={{ margin: 10, width: "100%" }} style={{margin: 10, width: "100%"}}
value={formData.area} value={formData.image}
keyboardType="numeric" onChangeText={(e) => setFormData({...formData, image: e})}
onChangeText={(e) => setFormData({ ...formData, area: e })} />
/> )}
<TextInput
mode="outlined"
label="Ludność" {picture && (
placeholder="Wpisz liczbę ludności" <View style={styles.imageContainer}>
style={{ margin: 10, width: "100%" }} <Image
value={formData.population} source={{uri: picture}}
keyboardType="numeric" style={styles.image}
onChangeText={(e) => setFormData({ ...formData, population: e })} />
/> </View>
<Button )}
style={{ margin: 10, width: "100%" }} <TextInput
icon="plus-circle-outline" mode="outlined"
mode={"contained"} label="Nazwa"
onPress={handleAddLocation} placeholder="Wpisz nazwę"
> style={{margin: 10, width: "100%"}}
Dodaj value={formData.name}
</Button> onChangeText={(e) => setFormData({...formData, name: e})}
<Button onPress={takePicture} style={{ margin: 10, width: "100%" }} icon="plus-circle-outline" mode={"contained"}> />
Zrób zdjęcie <TextInput
</Button> mode="outlined"
{picture && ( label="Opis"
<Image placeholder="Wpisz opis"
source={{ uri: picture.uri }} style={{margin: 10, width: "100%"}}
style={styles.image} multiline={true}
/> value={formData.description}
)} onChangeText={(e) => setFormData({...formData, description: e})}
</ScrollView> />
</KeyboardAvoidingView> <TextInput
); mode="outlined"
label="Powierzchnia"
placeholder="Wpisz powierzchnię"
style={{margin: 10, width: "100%"}}
value={formData.area}
keyboardType="numeric"
onChangeText={(e) => setFormData({...formData, area: e})}
/>
<TextInput
mode="outlined"
label="Ludność"
placeholder="Wpisz liczbę ludności"
style={{margin: 10, width: "100%"}}
value={formData.population}
keyboardType="numeric"
onChangeText={(e) => setFormData({...formData, population: e})}
/>
<Button
style={{margin: 10, width: "100%"}}
icon="plus-circle-outline"
mode={"contained"}
onPress={handleAddLocation}
>
Dodaj
</Button>
</ScrollView>
</KeyboardAvoidingView>
);
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
backgroundColor: "#25292e", backgroundColor: "#25292e",
justifyContent: "flex-start", justifyContent: "flex-start",
alignItems: "center", alignItems: "center",
}, padding: 10,
image: { },
width: 100, imageContainer: {
height: 100, width: "100%",
borderStyle: "solid", alignItems: "flex-start",
borderColor: "#fff", marginVertical: 10,
borderWidth: 1, },
}, image: {
}); width: 150,
height: 150,
borderStyle: "solid",
borderColor: "#fff",
borderWidth: 1,
borderRadius: 5,
},
imageMethodButtons: {
flexDirection: 'row',
justifyContent: 'space-between',
width: '100%',
marginVertical: 10,
},
methodButton: {
flex: 1,
marginHorizontal: 5,
},
label: {
marginTop: 10,
marginBottom: 10,
color: "#fff",
verticalAlign: "middle",
textAlign: "center",
}
});

View File

@@ -20,10 +20,10 @@ export default function Index() {
keyExtractor={(item) => item.id.toString()} keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => ( renderItem={({ item }) => (
<Card style={{ margin: 10 }}> <Card style={{ margin: 10 }}>
<Card.Cover <Card.Cover
style={{ marginBottom: 10 }} style={{ marginBottom: 10 }}
source={{ uri: item.image }} source={item.imageSource}
/> />
<Card.Content style={{ marginBottom: 10 }}> <Card.Content style={{ marginBottom: 10 }}>
<Text variant="titleLarge">{item.name}</Text> <Text variant="titleLarge">{item.name}</Text>
<Text variant="bodyMedium"> <Text variant="bodyMedium">

View File

@@ -1,4 +1,4 @@
import { View, ScrollView, StyleSheet, ActivityIndicator } from "react-native"; import { View, ScrollView, StyleSheet } from "react-native";
import { useEffect } from "react"; import { useEffect } from "react";
import { useTheme, Text, Card } from "react-native-paper"; import { useTheme, Text, Card } from "react-native-paper";
import { useLocalSearchParams } from "expo-router"; import { useLocalSearchParams } from "expo-router";
@@ -33,7 +33,7 @@ export default function Location() {
<Card style={{ margin: 10 }}> <Card style={{ margin: 10 }}>
<Card.Cover <Card.Cover
style={{ marginBottom: 10 }} style={{ marginBottom: 10 }}
source={{ uri: location.image }} source={location.imageSource}
/> />
<Card.Content style={{ marginBottom: 10 }}> <Card.Content style={{ marginBottom: 10 }}>
<Text variant="headlineLarge" style={{ marginBottom: 10 }}> <Text variant="headlineLarge" style={{ marginBottom: 10 }}>