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