Pobieranie i wysyłanie zdjęć i GPS
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
const API_URL = "https://hopp.zikor.pl";
|
const API_URL = "http://192.168.0.118:9000";
|
||||||
|
|
||||||
export async function listLocations() {
|
export async function listLocations() {
|
||||||
try {
|
try {
|
||||||
@@ -9,7 +9,6 @@ export async function listLocations() {
|
|||||||
return "No locations found";
|
return "No locations found";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Нормализуем изображения в полученных данных
|
|
||||||
return response.data.map(location => ({
|
return response.data.map(location => ({
|
||||||
...location,
|
...location,
|
||||||
imageSource: normalizeImageSource(location.image)
|
imageSource: normalizeImageSource(location.image)
|
||||||
@@ -23,23 +22,18 @@ export async function listLocations() {
|
|||||||
const normalizeImageSource = (image) => {
|
const normalizeImageSource = (image) => {
|
||||||
if (!image) return null;
|
if (!image) return null;
|
||||||
|
|
||||||
// Проверка, является ли изображение URL
|
|
||||||
if (typeof image === 'string') {
|
if (typeof image === 'string') {
|
||||||
// Если строка начинается с http или https, это URL
|
|
||||||
if (image.startsWith('http://') || image.startsWith('https://')) {
|
if (image.startsWith('http://') || image.startsWith('https://')) {
|
||||||
return { uri: image };
|
return { uri: image };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка на base64
|
|
||||||
if (image.startsWith('data:image')) {
|
if (image.startsWith('data:image')) {
|
||||||
return { uri: image };
|
return { uri: image };
|
||||||
} else if (image.length > 100) {
|
} else if (image.length > 100) {
|
||||||
// Предполагаем, что это base64 без префикса
|
|
||||||
return { uri: `data:image/jpeg;base64,${image}` };
|
return { uri: `data:image/jpeg;base64,${image}` };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Возвращаем null для неправильного формата
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +41,8 @@ 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}`);
|
||||||
if (!location) {
|
if (!location) {
|
||||||
throw new Error("Location not found");
|
console.error("Could not find location");
|
||||||
|
return("Location not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...location.data, {image: normalizeImageSource(location.image)}];
|
return [...location.data, {image: normalizeImageSource(location.image)}];
|
||||||
@@ -65,7 +60,8 @@ export async function addLocation(location) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!response) {
|
if (!response) {
|
||||||
throw new Error("Failed to add location");
|
console.log("Error adding location");
|
||||||
|
return("Failed to add location");
|
||||||
}
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -80,7 +76,8 @@ export async function updateLocation(id, location) {
|
|||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
});
|
});
|
||||||
if (!response) {
|
if (!response) {
|
||||||
throw new Error("Failed to add location");
|
console.log("Failed to update location");
|
||||||
|
return("Failed to update location");
|
||||||
}
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -93,7 +90,8 @@ export async function deleteLocation(id) {
|
|||||||
try {
|
try {
|
||||||
const response = await axios.delete(`${API_URL}/locations/${id}`);
|
const response = await axios.delete(`${API_URL}/locations/${id}`);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
throw new Error("Failed to delete location");
|
console.log("Error deleting location");
|
||||||
|
return("Failed to delete location");
|
||||||
}
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
6
app.json
6
app.json
@@ -33,6 +33,12 @@
|
|||||||
{
|
{
|
||||||
"photosPermission": "The app accesses your photos to let you share them with your friends."
|
"photosPermission": "The app accesses your photos to let you share them with your friends."
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"expo-location",
|
||||||
|
{
|
||||||
|
"locationAlwaysAndWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location."
|
||||||
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import {
|
|||||||
ScrollView,
|
ScrollView,
|
||||||
Image,
|
Image,
|
||||||
View,
|
View,
|
||||||
|
Alert,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import {TextInput, Button, Snackbar, Text} from "react-native-paper";
|
import {TextInput, Button, Snackbar, Text, useTheme} from "react-native-paper";
|
||||||
import {requestCameraPermissionsAsync, launchCameraAsync} from 'expo-image-picker';
|
import {requestCameraPermissionsAsync, launchCameraAsync} from 'expo-image-picker';
|
||||||
|
import * as Location from 'expo-location';
|
||||||
import useLocationStore from "@/locationStore";
|
import useLocationStore from "@/locationStore";
|
||||||
|
|
||||||
export default function FormScreen() {
|
export default function FormScreen() {
|
||||||
@@ -18,11 +20,15 @@ export default function FormScreen() {
|
|||||||
image: "",
|
image: "",
|
||||||
area: 0,
|
area: 0,
|
||||||
population: 0,
|
population: 0,
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
});
|
});
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [picture, setPicture] = useState(null);
|
const [picture, setPicture] = useState(null);
|
||||||
const [imageMethod, setImageMethod] = useState(null); // null, 'link' или 'camera'
|
const [imageMethod, setImageMethod] = useState(null);
|
||||||
|
const [location, setLocation] = useState < Location.LocationObject | null > (null);
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
const addLocation = useLocationStore((state) => state.addLocation);
|
const addLocation = useLocationStore((state) => state.addLocation);
|
||||||
|
|
||||||
@@ -32,7 +38,9 @@ export default function FormScreen() {
|
|||||||
formData.description &&
|
formData.description &&
|
||||||
formData.image &&
|
formData.image &&
|
||||||
formData.area &&
|
formData.area &&
|
||||||
formData.population
|
formData.population &&
|
||||||
|
formData.latitude &&
|
||||||
|
formData.longitude
|
||||||
) {
|
) {
|
||||||
const newLocation = {
|
const newLocation = {
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
@@ -41,6 +49,8 @@ export default function FormScreen() {
|
|||||||
image: formData.image,
|
image: formData.image,
|
||||||
area: parseFloat(formData.area),
|
area: parseFloat(formData.area),
|
||||||
population: parseInt(formData.population),
|
population: parseInt(formData.population),
|
||||||
|
latitude: parseFloat(formData.latitude),
|
||||||
|
longitude: parseFloat(formData.longitude),
|
||||||
};
|
};
|
||||||
|
|
||||||
const added = await addLocation(newLocation);
|
const added = await addLocation(newLocation);
|
||||||
@@ -50,6 +60,8 @@ export default function FormScreen() {
|
|||||||
image: "",
|
image: "",
|
||||||
area: 0,
|
area: 0,
|
||||||
population: 0,
|
population: 0,
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0
|
||||||
});
|
});
|
||||||
setPicture(null);
|
setPicture(null);
|
||||||
setImageMethod(null);
|
setImageMethod(null);
|
||||||
@@ -83,11 +95,36 @@ export default function FormScreen() {
|
|||||||
if (!result.canceled && result.assets && result.assets.length > 0) {
|
if (!result.canceled && result.assets && result.assets.length > 0) {
|
||||||
const image = result.assets[0];
|
const image = result.assets[0];
|
||||||
setPicture(image.uri);
|
setPicture(image.uri);
|
||||||
console.log(image.base64);
|
|
||||||
setFormData({...formData, image: image.base64});
|
setFormData({...formData, image: image.base64});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getCurrentLocation() {
|
||||||
|
|
||||||
|
let {status} = await Location.requestForegroundPermissionsAsync();
|
||||||
|
if (status !== 'granted') {
|
||||||
|
console.log('Permission to access location was denied');
|
||||||
|
Alert.alert("Błąd", "Nie udało się uzyskać dostępu do pobierania lokalizacji");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let location = await Location.getCurrentPositionAsync({});
|
||||||
|
setLocation(location);
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
latitude: location.coords.latitude,
|
||||||
|
longitude: location.coords.longitude
|
||||||
|
});
|
||||||
|
Alert.alert("Sukces!", "Pobrano lokalizację\n" +
|
||||||
|
"Szerokość geograficzna: " + location.coords.latitude + "\n" +
|
||||||
|
"Długość geograficzna: " + location.coords.longitude);
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert("Błąd", "Nie udało się pobrać lokalizacji");
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const selectImageMethod = (method) => {
|
const selectImageMethod = (method) => {
|
||||||
setImageMethod(method);
|
setImageMethod(method);
|
||||||
if (method === 'camera') {
|
if (method === 'camera') {
|
||||||
@@ -103,7 +140,9 @@ export default function FormScreen() {
|
|||||||
style={{flex: 1}}
|
style={{flex: 1}}
|
||||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||||
>
|
>
|
||||||
<ScrollView contentContainerStyle={styles.container}>
|
<ScrollView contentContainerStyle={styles.container} contentInsetAdjustmentBehavior="automatic"
|
||||||
|
style={[{backgroundColor: theme.colors.background}]}
|
||||||
|
>
|
||||||
<Snackbar
|
<Snackbar
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onDismiss={() => setVisible(false)}
|
onDismiss={() => setVisible(false)}
|
||||||
@@ -193,6 +232,14 @@ export default function FormScreen() {
|
|||||||
>
|
>
|
||||||
Dodaj
|
Dodaj
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style={{margin: 10, width: "100%"}} mode={"contained"}
|
||||||
|
onPress={getCurrentLocation}
|
||||||
|
icon={location ? "check-circle-outline" : "map-marker-outline"}
|
||||||
|
>
|
||||||
|
Pobierz aktualną lokalizację
|
||||||
|
</Button>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
);
|
);
|
||||||
@@ -200,11 +247,10 @@ export default function FormScreen() {
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
// backgroundColor: "#25292e",
|
||||||
backgroundColor: "#25292e",
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
padding: 10,
|
padding: 10,
|
||||||
|
flexGrow: 1,
|
||||||
},
|
},
|
||||||
imageContainer: {
|
imageContainer: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@@ -215,7 +261,6 @@ const styles = StyleSheet.create({
|
|||||||
width: 150,
|
width: 150,
|
||||||
height: 150,
|
height: 150,
|
||||||
borderStyle: "solid",
|
borderStyle: "solid",
|
||||||
borderColor: "#fff",
|
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
},
|
},
|
||||||
@@ -232,7 +277,6 @@ const styles = StyleSheet.create({
|
|||||||
label: {
|
label: {
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
color: "#fff",
|
|
||||||
verticalAlign: "middle",
|
verticalAlign: "middle",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default function Location() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={[styles.container, {backgroundColor: theme.colors.background}]}>
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<Card style={{ margin: 10 }}>
|
<Card style={{ margin: 10 }}>
|
||||||
<Card.Cover
|
<Card.Cover
|
||||||
@@ -55,6 +55,9 @@ export default function Location() {
|
|||||||
<Text variant="bodyMedium" style={{ marginBottom: 10 }}>
|
<Text variant="bodyMedium" style={{ marginBottom: 10 }}>
|
||||||
Ludność: {location.population} osób
|
Ludność: {location.population} osób
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text variant="bodyMedium" style={{ marginBottom: 10 }}>
|
||||||
|
Współrzedne geograficzne: {location.latitude}, {location.longitude}
|
||||||
|
</Text>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card>
|
</Card>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -67,7 +70,6 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: "#25292e",
|
backgroundColor: "#25292e",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from "react";
|
import {useState, useEffect} from "react";
|
||||||
import {
|
import {
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Platform,
|
Platform,
|
||||||
@@ -7,13 +7,13 @@ import {
|
|||||||
View,
|
View,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { TextInput, Button, Snackbar, useTheme } from "react-native-paper";
|
import {TextInput, Button, Snackbar, useTheme} from "react-native-paper";
|
||||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
import {useLocalSearchParams, useRouter} from "expo-router";
|
||||||
import useLocationStore from "@/locationStore";
|
import useLocationStore from "@/locationStore";
|
||||||
|
|
||||||
export default function EditLocation() {
|
export default function EditLocation() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { id } = useLocalSearchParams();
|
const {id} = useLocalSearchParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
@@ -22,6 +22,8 @@ export default function EditLocation() {
|
|||||||
image: "",
|
image: "",
|
||||||
area: "",
|
area: "",
|
||||||
population: "",
|
population: "",
|
||||||
|
longitude: "",
|
||||||
|
latitude: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
@@ -39,6 +41,8 @@ export default function EditLocation() {
|
|||||||
image: data.image,
|
image: data.image,
|
||||||
area: data.area.toString(),
|
area: data.area.toString(),
|
||||||
population: data.population.toString(),
|
population: data.population.toString(),
|
||||||
|
longitude: data.longitude.toString(),
|
||||||
|
latitude: data.latitude.toString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -50,7 +54,9 @@ export default function EditLocation() {
|
|||||||
formData.description &&
|
formData.description &&
|
||||||
formData.image &&
|
formData.image &&
|
||||||
formData.area &&
|
formData.area &&
|
||||||
formData.population
|
formData.population &&
|
||||||
|
formData.longitude &&
|
||||||
|
formData.latitude
|
||||||
) {
|
) {
|
||||||
// console.log("Form data:", formData);
|
// console.log("Form data:", formData);
|
||||||
const response = await updateLocation(id, {
|
const response = await updateLocation(id, {
|
||||||
@@ -84,16 +90,16 @@ export default function EditLocation() {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[styles.container, { backgroundColor: theme.colors.background }]}
|
style={[styles.container, {backgroundColor: theme.colors.background}]}
|
||||||
>
|
>
|
||||||
<ActivityIndicator size="large" color={theme.colors.primary} />
|
<ActivityIndicator size="large" color={theme.colors.primary}/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
style={{ flex: 1 }}
|
style={{flex: 1}}
|
||||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||||
>
|
>
|
||||||
<ScrollView contentContainerStyle={styles.container}>
|
<ScrollView contentContainerStyle={styles.container}>
|
||||||
@@ -108,48 +114,66 @@ export default function EditLocation() {
|
|||||||
mode="outlined"
|
mode="outlined"
|
||||||
label="Nazwa"
|
label="Nazwa"
|
||||||
placeholder="Wpisz nazwę"
|
placeholder="Wpisz nazwę"
|
||||||
style={{ margin: 10, width: "100%" }}
|
style={{margin: 10, width: "100%"}}
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChangeText={(e) => setFormData({ ...formData, name: e })}
|
onChangeText={(e) => setFormData({...formData, name: e})}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
label="Opis"
|
label="Opis"
|
||||||
placeholder="Wpisz opis"
|
placeholder="Wpisz opis"
|
||||||
style={{ margin: 10, width: "100%" }}
|
style={{margin: 10, width: "100%"}}
|
||||||
multiline={true}
|
multiline={true}
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChangeText={(e) => setFormData({ ...formData, description: e })}
|
onChangeText={(e) => setFormData({...formData, description: e})}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
label="Link do zdjęcia"
|
label="Link do zdjęcia"
|
||||||
multiline={true}
|
multiline={true}
|
||||||
placeholder="Wpisz link do zdjęcia"
|
placeholder="Wpisz link do zdjęcia"
|
||||||
style={{ margin: 10, width: "100%" }}
|
style={{margin: 10, width: "100%"}}
|
||||||
value={formData.image}
|
value={formData.image}
|
||||||
onChangeText={(e) => setFormData({ ...formData, image: e })}
|
onChangeText={(e) => setFormData({...formData, image: e})}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
label="Powierzchnia"
|
label="Powierzchnia"
|
||||||
placeholder="Wpisz powierzchnię"
|
placeholder="Wpisz powierzchnię"
|
||||||
style={{ margin: 10, width: "100%" }}
|
style={{margin: 10, width: "100%"}}
|
||||||
value={formData.area}
|
value={formData.area}
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
onChangeText={(e) => setFormData({ ...formData, area: e })}
|
onChangeText={(e) => setFormData({...formData, area: e})}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
label="Ludność"
|
label="Ludność"
|
||||||
placeholder="Wpisz liczbę ludności"
|
placeholder="Wpisz liczbę ludności"
|
||||||
style={{ margin: 10, width: "100%" }}
|
style={{margin: 10, width: "100%"}}
|
||||||
value={formData.population}
|
value={formData.population}
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
onChangeText={(e) => setFormData({ ...formData, population: e })}
|
onChangeText={(e) => setFormData({...formData, population: e})}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
mode="outlined"
|
||||||
|
label="Długość geograficzna"
|
||||||
|
placeholder="Wpisz długość geograficzną"
|
||||||
|
style={{margin: 10, width: "100%"}}
|
||||||
|
value={formData.longitude}
|
||||||
|
keyboardType="numeric"
|
||||||
|
onChangeText={(e) => setFormData({...formData, longitude: e})}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
mode="outlined"
|
||||||
|
label="Szerokość geograficzna"
|
||||||
|
placeholder="Wpisz szerokość geograficzną"
|
||||||
|
style={{margin: 10, width: "100%"}}
|
||||||
|
value={formData.latitude}
|
||||||
|
keyboardType="numeric"
|
||||||
|
onChangeText={(e) => setFormData({...formData, latitude: e})}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
style={{ margin: 10, width: "100%" }}
|
style={{margin: 10, width: "100%"}}
|
||||||
icon="plus-circle-outline"
|
icon="plus-circle-outline"
|
||||||
mode={"contained"}
|
mode={"contained"}
|
||||||
onPress={handleEditLocation}
|
onPress={handleEditLocation}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"expo-router": "~5.0.7",
|
"expo-router": "~5.0.7",
|
||||||
"expo-status-bar": "~2.2.3",
|
"expo-status-bar": "~2.2.3",
|
||||||
"expo-image-picker": "~16.1.4",
|
"expo-image-picker": "~16.1.4",
|
||||||
|
"expo-location": "~18.1.5",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
"react-native": "0.79.2",
|
"react-native": "0.79.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user