151 lines
4.2 KiB
TypeScript
151 lines
4.2 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { supabase } from '@/lib/supabase'
|
|
import { StyleSheet, Alert, Image, TouchableOpacity } from 'react-native'
|
|
import * as ImagePicker from 'expo-image-picker'
|
|
//import { ImageManipulator } from 'expo-image-manipulator';
|
|
import { ThemedView, ThemedText, ThemedTextButton } from '../theme';
|
|
import { IconSymbol } from '@/components/ui/IconSymbol';
|
|
|
|
interface Props {
|
|
size: number
|
|
url: string | null
|
|
onUpload: (filePath: string) => void
|
|
}
|
|
|
|
export default function Avatar({ url, size = 150, onUpload }: Props) {
|
|
const [uploading, setUploading] = useState(false)
|
|
const [avatarUrl, setAvatarUrl] = useState<string | null>(null)
|
|
const avatarSize = { height: size, width: size }
|
|
|
|
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) {
|
|
if (error instanceof Error) {
|
|
console.log('Error downloading image: ', error.message)
|
|
}
|
|
}
|
|
};
|
|
|
|
async function uploadAvatar() {
|
|
try {
|
|
setUploading(true)
|
|
const { data: { user } } = await supabase.auth.getUser();
|
|
|
|
const result = await ImagePicker.launchImageLibraryAsync({
|
|
mediaTypes: ImagePicker.MediaTypeOptions.Images, // Restrict to only images
|
|
allowsMultipleSelection: false, // Can only select one image
|
|
allowsEditing: true, // Allows the user to crop / rotate their photo before uploading it
|
|
quality: 1,
|
|
exif: false, // We don't want nor need that data.
|
|
});
|
|
|
|
if (result.canceled || !result.assets || result.assets.length === 0) {
|
|
console.log('User cancelled image picker.');
|
|
return;
|
|
}
|
|
|
|
//const manipulatedImage = await ImageManipulator.manipulate(result.assets[0].uri)
|
|
//.resize({ width: 300 })
|
|
//.renderAsync();
|
|
|
|
//const manipulateResult = await manipulateAsync(
|
|
//result.assets[0].uri,
|
|
//[{resize: {width: 300, height: 300}}],
|
|
//{compress: 0.7, format: SaveFormat.JPEG}
|
|
//);
|
|
|
|
const image = result.assets[0];
|
|
console.log('Got image', image)
|
|
|
|
if (!image.uri) {
|
|
throw new Error('No image uri!') // Realistically, this should never happen, but just in case...
|
|
}
|
|
|
|
const arraybuffer = await fetch(image.uri).then((res) => res.arrayBuffer())
|
|
|
|
const fileExt = image.uri?.split('.').pop()?.toLowerCase() ?? 'jpeg'
|
|
const path = `${Date.now()}.${fileExt}`
|
|
const { data, error: uploadError } = await supabase.storage
|
|
.from('avatars')
|
|
.upload(path, arraybuffer, {
|
|
contentType: image.mimeType ?? 'image/jpeg',
|
|
});
|
|
const { data: updateData, error: updateError } = await supabase
|
|
.from('profiles')
|
|
.update({ avatar_url: data?.path })
|
|
.eq('id', user?.id);
|
|
|
|
if (uploadError) {
|
|
throw uploadError
|
|
}
|
|
|
|
onUpload(data.path)
|
|
} catch (error) {
|
|
if (error instanceof Error) {
|
|
Alert.alert(error.message)
|
|
} else {
|
|
throw error
|
|
}
|
|
} finally {
|
|
setUploading(false)
|
|
}
|
|
};
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
onPress={uploadAvatar}
|
|
style={styles.avatarContainer}
|
|
>
|
|
{avatarUrl ? (
|
|
<Image source={{ uri: avatarUrl }} style={styles.avatar} />
|
|
) : (
|
|
<ThemedView style={styles.avatarPlaceholder}>
|
|
<IconSymbol name="person.fill" size={50} color="#999" />
|
|
</ThemedView>
|
|
)}
|
|
<ThemedText style={styles.changePhotoText}>Change Photo</ThemedText>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
avatarContainer: {
|
|
alignItems: 'center',
|
|
marginTop: 20,
|
|
marginBottom: 30,
|
|
},
|
|
avatar: {
|
|
width: 120,
|
|
height: 120,
|
|
borderRadius: 60,
|
|
},
|
|
avatarPlaceholder: {
|
|
width: 120,
|
|
height: 120,
|
|
borderRadius: 60,
|
|
backgroundColor: '#E1E1E1',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
changePhotoText: {
|
|
marginTop: 8,
|
|
color: '#007AFF',
|
|
fontSize: 16,
|
|
},
|
|
});
|