377 lines
11 KiB
TypeScript
377 lines
11 KiB
TypeScript
import React, { useState, useEffect } from 'react'; // ADDED useEffect
|
|
import { // Ensure all necessary components are imported
|
|
View,
|
|
Text,
|
|
TouchableOpacity,
|
|
Image,
|
|
Alert,
|
|
StyleSheet,
|
|
TextInput,
|
|
ScrollView,
|
|
ActivityIndicator,
|
|
} from 'react-native';
|
|
import * as ImagePicker from 'expo-image-picker';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { useLocalSearchParams } from 'expo-router'; // ADDED: Import useLocalSearchParams
|
|
|
|
interface UploadResponse {
|
|
success: boolean;
|
|
message: string;
|
|
imageUrl?: string;
|
|
}
|
|
|
|
export default function UploadTab() {
|
|
// Get parameters from the route
|
|
const params = useLocalSearchParams();
|
|
// Ensure placeId from params is treated as a string, provide fallback if not present
|
|
const initialPlaceId = typeof params.placeId === 'string' ? params.placeId : '';
|
|
const initialPlaceName = typeof params.placeName === 'string' ? params.placeName : 'Selected Place'; // For display
|
|
|
|
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
|
// Initialize placeId state with the value from params
|
|
const [placeId, setPlaceId] = useState<string>(initialPlaceId);
|
|
const [placeNameDisplay, setPlaceNameDisplay] = useState<string>(initialPlaceName); // State for displaying name
|
|
const [notes, setNotes] = useState<string>('');
|
|
const [uploading, setUploading] = useState<boolean>(false);
|
|
|
|
const BACKEND_URL = 'http://132.145.65.145:8080';
|
|
|
|
// Use useEffect to update placeId and placeNameDisplay if params change
|
|
// This handles cases where the component might already be mounted and params are updated
|
|
useEffect(() => {
|
|
if (typeof params.placeId === 'string' && params.placeId !== placeId) {
|
|
setPlaceId(params.placeId);
|
|
}
|
|
if (typeof params.placeName === 'string' && params.placeName !== placeNameDisplay) {
|
|
setPlaceNameDisplay(params.placeName);
|
|
}
|
|
}, [params.placeId, params.placeName]); // Depend on params.placeId and placeName
|
|
|
|
const pickImage = async () => {
|
|
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
if (status !== 'granted') {
|
|
Alert.alert('Permission Required', 'Sorry, we need camera roll permissions to upload images.');
|
|
return;
|
|
}
|
|
const result = await ImagePicker.launchImageLibraryAsync({
|
|
mediaTypes: ImagePicker.MediaTypeOptions.Images, // Use MediaTypeOptions.Images directly
|
|
allowsEditing: true,
|
|
aspect: [4, 3],
|
|
quality: 0.8,
|
|
allowsMultipleSelection: false,
|
|
});
|
|
if (!result.canceled && result.assets && result.assets.length > 0) {
|
|
setSelectedImage(result.assets[0].uri);
|
|
}
|
|
};
|
|
|
|
const takePhoto = async () => {
|
|
const { status } = await ImagePicker.requestCameraPermissionsAsync();
|
|
if (status !== 'granted') {
|
|
Alert.alert('Permission Required', 'Sorry, we need camera permissions to take photos.');
|
|
return;
|
|
}
|
|
const result = await ImagePicker.launchCameraAsync({
|
|
allowsEditing: true,
|
|
aspect: [4, 3],
|
|
quality: 0.8,
|
|
});
|
|
if (!result.canceled && result.assets && result.assets.length > 0) {
|
|
setSelectedImage(result.assets[0].uri);
|
|
}
|
|
};
|
|
|
|
const uploadImage = async () => {
|
|
if (!selectedImage) {
|
|
Alert.alert('Error', 'Please select an image first');
|
|
return;
|
|
}
|
|
if (!placeId) { // Ensure placeId is not empty after potential navigation
|
|
Alert.alert('Error', 'Place ID is missing. Please select a mosque first.');
|
|
return;
|
|
}
|
|
|
|
setUploading(true);
|
|
|
|
try {
|
|
console.log('Uploading for placeId:', placeId);
|
|
console.log('Notes:', notes);
|
|
|
|
const formData = new FormData();
|
|
const imageFile = {
|
|
uri: selectedImage,
|
|
type: 'image/jpeg',
|
|
name: 'image.jpg',
|
|
} as any;
|
|
|
|
formData.append('image', imageFile);
|
|
formData.append('place_id', placeId.trim()); // Ensure this matches backend expected field name
|
|
formData.append('Notes', notes.trim()); // Ensure this matches backend expected field name
|
|
|
|
console.log('Sending form data with keys:', Array.from(formData.keys()));
|
|
for (let [key, value] of formData.entries()) {
|
|
console.log(key, value);
|
|
}
|
|
|
|
const response = await fetch(`${BACKEND_URL}/images/upload`, {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
// DO NOT set 'Content-Type' for FormData, fetch sets it automatically with boundary
|
|
},
|
|
});
|
|
|
|
let result: UploadResponse;
|
|
const responseCopy = response.clone(); // Clone response to read body twice if needed
|
|
try {
|
|
result = await response.json();
|
|
} catch (error) {
|
|
const text = await responseCopy.text();
|
|
throw new Error(text || "Unknown error parsing JSON response");
|
|
}
|
|
|
|
if (response.ok && result.success) {
|
|
Alert.alert('Success', 'Image uploaded successfully!');
|
|
setSelectedImage(null);
|
|
setPlaceId(initialPlaceId); // Reset to initial placeId from params, or empty if not from params
|
|
setPlaceNameDisplay(initialPlaceName); // Reset display name
|
|
setNotes('');
|
|
} else {
|
|
Alert.alert('Upload Failed', result.message || 'Failed to upload image. Please check backend logs.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Upload error:', error);
|
|
Alert.alert('Error', `Upload failed: ${error.message || 'Network error'}.`);
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
};
|
|
|
|
const clearImage = () => {
|
|
setSelectedImage(null);
|
|
};
|
|
|
|
return (
|
|
<ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
|
|
<Text style={styles.title}>Upload Image</Text>
|
|
|
|
{/* Display Place Name (User friendly) */}
|
|
<Text style={styles.placeNameDisplay}>For: {placeNameDisplay || 'No place selected'}</Text>
|
|
|
|
{/* Image Selection Section */}
|
|
<View style={styles.imageSection}>
|
|
{selectedImage ? (
|
|
<View style={styles.imageContainer}>
|
|
<Image source={{ uri: selectedImage }} style={styles.selectedImage} />
|
|
<TouchableOpacity style={styles.clearButton} onPress={clearImage}>
|
|
<Ionicons name="close-circle" size={24} color="#ff4444" />
|
|
</TouchableOpacity>
|
|
</View>
|
|
) : (
|
|
<View style={styles.placeholderContainer}>
|
|
<Ionicons name="image-outline" size={64} color="#ccc" />
|
|
<Text style={styles.placeholderText}>No image selected</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
|
|
{/* Image Selection Buttons */}
|
|
<View style={styles.buttonRow}>
|
|
<TouchableOpacity style={styles.actionButton} onPress={pickImage}>
|
|
<Ionicons name="images-outline" size={20} color="#007AFF" />
|
|
<Text style={styles.buttonText}>Gallery</Text>
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity style={styles.actionButton} onPress={takePhoto}>
|
|
<Ionicons name="camera-outline" size={20} color="#007AFF" />
|
|
<Text style={styles.buttonText}>Camera</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Form Section */}
|
|
<View style={styles.formSection}>
|
|
<Text style={styles.label}>Notes</Text>
|
|
<TextInput
|
|
style={[styles.input, styles.notesInput]}
|
|
value={notes}
|
|
onChangeText={setNotes}
|
|
placeholder="Add notes about this image (optional)"
|
|
placeholderTextColor="#999"
|
|
multiline
|
|
numberOfLines={3}
|
|
textAlignVertical="top"
|
|
/>
|
|
</View>
|
|
|
|
{/* Upload Button */}
|
|
<TouchableOpacity
|
|
style={[styles.uploadButton, (!selectedImage || uploading || !placeId) && styles.uploadButtonDisabled]} // Disable if placeId is missing
|
|
onPress={uploadImage}
|
|
disabled={!selectedImage || uploading || !placeId} // Disable if placeId is missing
|
|
>
|
|
{uploading ? (
|
|
<ActivityIndicator color="#fff" />
|
|
) : (
|
|
<>
|
|
<Ionicons name="cloud-upload-outline" size={20} color="#fff" />
|
|
<Text style={styles.uploadButtonText}>Upload Image</Text>
|
|
</>
|
|
)}
|
|
</TouchableOpacity>
|
|
|
|
{/* Instructions */}
|
|
<View style={styles.instructionsContainer}>
|
|
<Text style={styles.instructionsTitle}>Instructions:</Text>
|
|
<Text style={styles.instructionsText}>
|
|
1. Select an image from your gallery or take a photo{'\n'}
|
|
2. Add optional notes{'\n'}
|
|
3. Tap Upload
|
|
</Text>
|
|
</View>
|
|
</ScrollView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#f8f9fa',
|
|
},
|
|
contentContainer: {
|
|
padding: 20,
|
|
},
|
|
title: {
|
|
fontSize: 24,
|
|
fontWeight: 'bold',
|
|
marginBottom: 20,
|
|
textAlign: 'center',
|
|
color: '#333',
|
|
},
|
|
placeNameDisplay: { // New style to display the place name
|
|
fontSize: 18,
|
|
fontWeight: 'bold',
|
|
marginBottom: 15,
|
|
textAlign: 'center',
|
|
color: '#555',
|
|
},
|
|
imageSection: {
|
|
marginBottom: 20,
|
|
},
|
|
imageContainer: {
|
|
position: 'relative',
|
|
alignItems: 'center',
|
|
},
|
|
selectedImage: {
|
|
width: 300,
|
|
height: 200,
|
|
borderRadius: 10,
|
|
resizeMode: 'cover',
|
|
},
|
|
clearButton: {
|
|
position: 'absolute',
|
|
top: 10,
|
|
right: 10,
|
|
backgroundColor: '#fff',
|
|
borderRadius: 12,
|
|
},
|
|
placeholderContainer: {
|
|
height: 200,
|
|
backgroundColor: '#f0f0f0',
|
|
borderRadius: 10,
|
|
borderWidth: 2,
|
|
borderColor: '#ddd',
|
|
borderStyle: 'dashed',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
placeholderText: {
|
|
marginTop: 10,
|
|
color: '#999',
|
|
fontSize: 16,
|
|
},
|
|
buttonRow: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-around',
|
|
marginBottom: 30,
|
|
},
|
|
actionButton: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
backgroundColor: '#e3f2fd',
|
|
paddingHorizontal: 20,
|
|
paddingVertical: 12,
|
|
borderRadius: 8,
|
|
borderWidth: 1,
|
|
borderColor: '#007AFF',
|
|
},
|
|
buttonText: {
|
|
marginLeft: 8,
|
|
color: '#007AFF',
|
|
fontSize: 16,
|
|
fontWeight: '500',
|
|
},
|
|
formSection: {
|
|
marginBottom: 30,
|
|
},
|
|
label: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
marginBottom: 8,
|
|
color: '#333',
|
|
},
|
|
input: {
|
|
backgroundColor: '#fff',
|
|
borderWidth: 1,
|
|
borderColor: '#ddd',
|
|
borderRadius: 8,
|
|
paddingHorizontal: 15,
|
|
paddingVertical: 12,
|
|
fontSize: 16,
|
|
marginBottom: 15,
|
|
},
|
|
readOnlyInput: { // NEW STYLE for read-only text input
|
|
backgroundColor: '#e9ecef', // Lighter background for read-only
|
|
color: '#6c757d', // Slightly greyed out text
|
|
},
|
|
notesInput: {
|
|
height: 80,
|
|
paddingTop: 12,
|
|
},
|
|
uploadButton: {
|
|
backgroundColor: '#28a745',
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingVertical: 15,
|
|
borderRadius: 8,
|
|
marginBottom: 30,
|
|
},
|
|
uploadButtonDisabled: {
|
|
backgroundColor: '#ccc',
|
|
},
|
|
uploadButtonText: {
|
|
color: '#fff',
|
|
fontSize: 18,
|
|
fontWeight: 'bold',
|
|
marginLeft: 8,
|
|
},
|
|
instructionsContainer: {
|
|
backgroundColor: '#fff',
|
|
padding: 15,
|
|
borderRadius: 8,
|
|
borderLeftWidth: 4,
|
|
borderLeftColor: '#007AFF',
|
|
},
|
|
instructionsTitle: {
|
|
fontSize: 16,
|
|
fontWeight: 'bold',
|
|
marginBottom: 8,
|
|
color: '#333',
|
|
},
|
|
instructionsText: {
|
|
fontSize: 14,
|
|
color: '#666',
|
|
lineHeight: 20,
|
|
},
|
|
}); |