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