import { useState, useEffect } from 'react'; import { supabase } from '@/lib/supabase'; import { StyleSheet, Alert, Image, TouchableOpacity, ActivityIndicator } from 'react-native'; import * as ImagePicker from 'expo-image-picker'; import * as FileSystem from 'expo-file-system'; import { manipulateAsync, SaveFormat } from 'expo-image-manipulator'; import { ThemedView, ThemedText } from '../theme'; import { IconSymbol } from '@/components/ui/IconSymbol'; interface AvatarProps { size?: number; url: string | null; onUpload?: (filePath: string) => void; disabled?: boolean; } export default function ProfileAvatar({ url, size = 120, onUpload, disabled = false }: AvatarProps) { const [uploading, setUploading] = useState(false); const [avatarUrl, setAvatarUrl] = useState(null); useEffect(() => { if (url) downloadImage(url); }, [url]); async function downloadImage(path: string) { try { const { data, error } = await supabase.storage.from('avatars').download(path); if (error) throw error; const fr = new FileReader(); fr.readAsDataURL(data); fr.onload = () => { setAvatarUrl(fr.result as string); }; } catch (error) { console.log('Error downloading image: ', error instanceof Error ? error.message : error); } } async function uploadAvatar() { if (disabled || uploading) return; try { setUploading(true); // Get current user const { data: { user } } = await supabase.auth.getUser(); if (!user) throw new Error('User not authenticated'); // Request permission if needed const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (!permissionResult.granted) { Alert.alert('Permission Required', 'Please allow access to your photo library to upload an avatar.'); return; } // Launch image picker const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsMultipleSelection: false, allowsEditing: true, aspect: [1, 1], quality: 0.8, exif: false, }); if (result.canceled || !result.assets || result.assets.length === 0) { return; } const image = result.assets[0]; // Compress and resize the image const compressedImage = await manipulateAsync( image.uri, [{ resize: { width: 300, height: 300 } }], { compress: 0.7, format: SaveFormat.JPEG } ); // Get file info to check size const fileInfo = await FileSystem.getInfoAsync(compressedImage.uri); // Convert to array buffer for upload const arraybuffer = await fetch(compressedImage.uri).then((res) => res.arrayBuffer()); // Generate unique filename const fileExt = compressedImage.uri.split('.').pop()?.toLowerCase() ?? 'jpg'; const fileName = `${user.id}_${Date.now()}.${fileExt}`; // Upload to Supabase Storage const { data, error: uploadError } = await supabase.storage .from('avatars') .upload(fileName, arraybuffer, { contentType: `image/${fileExt}`, upsert: true, }); if (uploadError) throw uploadError; // Update user profile with new avatar URL const { error: updateError } = await supabase .from('profiles') .update({ avatar_url: data.path, updated_at: new Date() }) .eq('id', user.id); if (updateError) throw updateError; // Set the new avatar URL setAvatarUrl(compressedImage.uri); // Call the onUpload callback if provided if (onUpload) onUpload(data.path); Alert.alert('Success', 'Avatar updated successfully!'); } catch (error) { Alert.alert('Error uploading avatar', error instanceof Error ? error.message : 'An unknown error occurred'); } finally { setUploading(false); } } return ( {avatarUrl ? ( ) : ( )} {uploading ? ( ) : ( disabled ? ( ) : ( Change Photo ) )} ); } const styles = StyleSheet.create({ avatarContainer: { alignItems: 'center', }, avatar: { backgroundColor: '#E1E1E1', }, avatarPlaceholder: { backgroundColor: '#E1E1E1', justifyContent: 'center', alignItems: 'center', }, changePhotoText: { marginTop: 8, color: '#007AFF', fontSize: 16, }, uploadingIndicator: { marginTop: 8, } });