1import MaterialIcons from '@expo/vector-icons/build/MaterialIcons'; 2import * as FileSystem from 'expo-file-system'; 3import * as MediaLibrary from 'expo-media-library'; 4import * as Permissions from 'expo-permissions'; 5import React from 'react'; 6import { 7 ScrollView, 8 StyleSheet, 9 Text, 10 TouchableOpacity, 11 TouchableOpacityProps, 12 View, 13 Platform, 14} from 'react-native'; 15 16import Photo from './Photo'; 17 18const PHOTOS_DIR = FileSystem.documentDirectory + 'photos'; 19 20interface State { 21 selected: string[]; 22} 23 24function useLoadedPhotos() { 25 const [photos, setPhotos] = React.useState<string[]>([]); 26 27 React.useEffect(() => { 28 let isMounted = true; 29 if (Platform.OS !== 'web') { 30 FileSystem.readDirectoryAsync(PHOTOS_DIR).then(photos => { 31 if (isMounted) { 32 setPhotos(photos); 33 } 34 }); 35 } 36 return () => { 37 isMounted = false; 38 }; 39 }, []); 40 return photos; 41} 42 43export default function GalleryScreen(props: TouchableOpacityProps & { photos?: string[] }) { 44 const photos = useLoadedPhotos(); 45 return <LoadedGalleryScreen {...props} photos={photos.length ? photos : props.photos ?? []} />; 46} 47 48class LoadedGalleryScreen extends React.Component< 49 TouchableOpacityProps & { photos: string[] }, 50 State 51> { 52 readonly state: State = { 53 selected: [], 54 }; 55 56 toggleSelection = (uri: string, isSelected: boolean) => { 57 this.setState(({ selected }) => { 58 if (isSelected) { 59 selected.push(uri); 60 } else { 61 selected = selected.filter(item => item !== uri); 62 } 63 return { selected }; 64 }); 65 }; 66 67 saveToGallery = async () => { 68 const photos = this.state.selected; 69 70 if (photos.length > 0) { 71 const { status } = await Permissions.askAsync(Permissions.MEDIA_LIBRARY); 72 73 if (status !== 'granted') { 74 throw new Error('Denied MEDIA_LIBRARY permissions!'); 75 } 76 77 const promises = photos.map(photoUri => { 78 return MediaLibrary.createAssetAsync(photoUri); 79 }); 80 81 await Promise.all(promises); 82 alert("Successfully saved photos to user's gallery!"); 83 } else { 84 alert('No photos to save!'); 85 } 86 }; 87 88 renderPhoto = (fileName: string) => ( 89 <Photo 90 key={fileName} 91 uri={Platform.select({ web: fileName, default: `${PHOTOS_DIR}/${fileName}` })} 92 onSelectionToggle={this.toggleSelection} 93 /> 94 ); 95 96 render() { 97 return ( 98 <View style={styles.container}> 99 <View style={styles.navbar}> 100 <TouchableOpacity style={styles.button} onPress={this.props.onPress}> 101 <MaterialIcons name="arrow-back" size={25} color="white" /> 102 </TouchableOpacity> 103 <TouchableOpacity style={styles.button} onPress={this.saveToGallery}> 104 <Text style={styles.whiteText}>Save selected to gallery</Text> 105 </TouchableOpacity> 106 </View> 107 <ScrollView contentContainerStyle={{ flex: 1 }}> 108 <View style={styles.pictures}>{this.props.photos.map(this.renderPhoto)}</View> 109 </ScrollView> 110 </View> 111 ); 112 } 113} 114 115const styles = StyleSheet.create({ 116 container: { 117 flex: 1, 118 backgroundColor: 'white', 119 }, 120 navbar: { 121 flexDirection: 'row', 122 alignItems: 'center', 123 justifyContent: 'space-between', 124 backgroundColor: '#4630EB', 125 }, 126 pictures: { 127 flex: 1, 128 flexWrap: 'wrap', 129 flexDirection: 'row', 130 justifyContent: 'space-around', 131 paddingVertical: 8, 132 }, 133 button: { 134 padding: 20, 135 }, 136 whiteText: { 137 color: 'white', 138 }, 139}); 140